|
Конструктор PT4TaskMakerX для C++
Общее описание
Конструктор PT4TaskMakerX является упрощенным вариантом базового конструктора
PT4TaskMaker для языка C++. Использованные в нем возможности C++,
такие как шаблоны и макросы, делают процесс разработки и модификации новых групп заданий для электронного
задачника Programming Taskbook более простым и наглядным.
Конструктор PT4TaskMakerX входит в состав комплекса TeacherPack, начиная с версии 3.4.
Конструктор PT4TaskMakerX позволяет использовать все возможности базовых вариантов конструкторов,
за исключением средств, связанных с добавлением в задание указателей и динамических структур данных,
а также с документированием создаваемой группы (т. е. с определением
дополнительных описаний, включаемых в преамбулы группы и ее подгрупп). Это объясняется тем, что необходимость
в использовании указателей возникает только при разработке специализированных групп, а
документирование групп в последних версиях задачника удобнее оформлять в виде файлов дополнений.
Имя cpp-файла с реализацией группы заданий должно совпадать с названием этой группы.
Добавлять к имени файла префикс PT4 не требуется, но скомпилированная динамическая библиотека,
как и в случае использования базового варианта конструктора PT4TaskMaker, должна обладать этим префиксом.
Библиотека должна содержать обязательную функцию с модификатором __stdcall и фиксированным именем
inittaskgroup, причем в этой функции должна вызываться функция
NewGroup. Кроме того, в начале программы необходимо вызвать специальный макрос DefineGroupName.
Этот макрос позволит библиотеке получить имя группы из названия исходного cpp-файла.
Подключать специальные пространства имен не требуется.
Для включения в группу нового задания используется
макрос DefineTask(TaskName). Данный макрос определяет функцию,
тело которой должно располагаться в коде сразу после макроса и содержать реализацию задания.
Задания включаются в группу в том порядке, в котором они определены в коде.
Имя, переданное в качестве параметра в макрос DefineTask,
должно соответствовать правилам именования функций в языке C++. Для разных заданий нельзя использовать одинаковые имена.
Все функции конструктора PT4TaskMakerX доступны в виде статических методов
класса pt, что упрощает как ознакомление с ними при использовании редактора Visual Studio Code:
достаточно ввести текст pt:: , чтобы получить список всех методов данного класса.
При выборе метода из списка доступна контекстная подсказка, содержащая
краткое описание метода на английском и русском языке и его сигнатуру.
Класс pt также содержит набор констант, которые могут использоваться в некоторых его методах.
Обзор компонентов конструктора PT4TaskMakerX
Все компоненты конструктора PT4TaskMakerX (за исключением типа ShortString) являются статическими константами или функциями-членами класса pt,
и при их описании это особо не оговаривается.
В качестве строковых параметров функций и строковых возвращаемых значений используются
данные типа std::string (в дальнейшем этот тип будет указываться без пространства имен std).
Исключение составляют параметры, которые обычно указываются в виде строковых литералов (а не переменных);
они описываются как const char* и используются в функциях NewGroup, UseTask, NewTask. Кроме того,
тип const char* имеют все параметры, связанные с комментариями (для них используются имена comm, comm1, comm2 и comm3).
Параметры-типы T, T1, T2, T3, указываемые в шаблонных функциях, могут соответствовать
всем типам данных, которые допустимо использовать в задачнике, а именно, bool, int, double, char, string.
Попытка указания других типов (в частности строкового литерала типа const char *) может приводить либо
к ошибкам при компиляции или сборке проекта, либо к ошибкам при отображении данных в окне задачника.
В отличие от базовых вариантов конструктора, при определении исходных и контрольных данных
можно использовать контейнеры std::vector<T>, где в качестве типа T допустимо указывать типы, перечисленные
в предыдущем абзаце (bool, int, double, char, string), и их обертки типа Seq<T> из заголовочного файла linqcpp.h,
также входящего в состав конструктора PT4TaskMakerX. В дальнейшем тип std::vector<T> будет указываться без
пространства имен std.
Тип ShortString и константы
using ShortString = char[256];
Псевдоним специального строкового типа, используемого для формирования двоичных строковых файлов.
Тип ShortString представляет собой
буфер типа char[256], в котором первый байт отводится под длину строки, а остальные байты под символы этой
строки (завершающая часть буфера может содержать произвольные символы). Данный формат обусловлен особенностями
базового варианта конструктора PT4TaskMaker. Для преобразования обычной строки string в строку ShortString
предусмотрена функция ConvertToShortString, которая описывается далее.
const int OptionUseAddition = 8; // группа доступна только при наличии файла дополнений
const int OptionHideExamples = 16; // не отображать раздел с примером верного решения
Набор констант, позволяющих настроить дополнительные свойства создаваемой группы
(эти константы указываются в качестве последнего, необязательного параметра функции NewGroup и
объединяются операцией |).
const int PosLeft = 100; // выравнивание по центру левой половины окна задачника
const int PosCenter = 150; // выравнивание по центру окна задачника
const int PosRight = 200; // выравнивание по центру правой половины окна задачника
const int PosHide = 255; // скрытие элемента данных в окне задачника
Набор констант, позволяющих настроить дополнительные визуальные характеристики элементов данных,
определяемых с помощью функций DataPos и ResPos.
В конструкторе PT4TaskMakerX можно использовать константы lgXXX
базового конструктора, связанные с различными языками программирования, поддерживаемыми задачником Programming Taskbook,
и наборами языков.
Определение общих характеристик группы заданий
DefineGroupName;
void NewGroup(const char *GroupDescription, const char *GroupAuthor, const char *GroupKey, int Options = 0);
Макрос и вспомогательная функция, которые обязательно должны использоваться в библиотеке,
определяющей группу.
Функция NewGroup должна вызываться в функции inittaskgroup (без параметров).
Первый параметр функции NewGroup содержит краткое описание определяемой
группы, второй сведения о разработчике (данный параметр может быть пустой строкой),
третий ключ группы, четвертый (необязательный) набор констант
OptionUseAddition и OptionHideExamples,
определяющих дополнительные свойства группы (константы объединяются операциями
or). Краткое описание группы используется в качестве заголовка в ее html-описании.
Если определяется группа заданий для русской или комбинированной локали (см. описание функции CurrentLocale),
то параметр GroupDescription должен определять как английское, так и русское описание группы в следующем формате:
"Английское описание|Русское описание" . См. также примеры кода в разделе
«Структура основного файла проекта для библиотеки, определяющей группу заданий».
Импортирование существующих заданий в новую группу
void UseTask(const char *GroupName, int TaskNumber);
void UseTask(const char *GroupName, int TaskNumber, const char *TopicDescription);
Функция, позволяющая включать в группу задание из другой группы.
Должна вызываться в функции, связанной с макросом DefineTask.
Первый параметр определяет имя существующей группы, второй номер задания из этой группы.
Третий, необязательный параметр позволяет изменить заголовок, связываемый с импортируемым заданием.
Если данный параметр отсутствует или является пустой строкой, то для импортируемого
задания используется его исходный заголовок.
В параметре GroupName после имени группы можно дополнительно указывать поправку для вычисления
ссылки на другое задание (поправка является целым числом и отделяется от имени группы
символом #). Например, если в группу Demo в качестве задания Demo10 импортируется
задание Proc46, а в качестве Demo11 задание Proc49, ссылающееся на Proc46, то при импортировании
задания Proc49 необходимо указать поправку, равную 2. Если этого не сделать, то в формулировке
задания Demo11 будет указана ссылка не на задание Demo10, а на задание Demo8 (поскольку оно находится
«на том же расстоянии» от задания Demo11, что и задание Proc46 относительно задания Proc49).
Добавление поправки 2 должно быть оформлено следующим образом: pt::UseTask("Proc#2", 49) .
Базовые функции для определения новых заданий
void NewTask(const char *Topic, const char *TaskText);
void NewTask(const char *TaskText);
Данная функция должна вызываться первой при определении нового задания (кроме
случаев, когда задание импортируется из существующей группы функцией UseTask).
Параметр TaskText определяет формулировку задания, состоящую из нескольких строк текста, длина которых не превышает 78 символов
и разделенным символом разрыва строки «\n».
В формулировке задания могут использоваться все виды управляющих последовательностей, описанные в разделе
«Форматирование текста заданий».
При определении формулировки заданий рекомендуется
использовать дословные строки (raw string literals),
которые позволяют избежать экранирования символов «\» в управляющих последовательностях конструктора
и не требуют явного использования символа «\n» для указания разрыва строки
(достаточно просто перенести часть строки на новую экранную строку).
Пример дословной строки: R"(\IHello,\ world\i)" (следует обратить внимание на то,
что в начале и в конце текста, содержащегося в кавычках, необходимо
указывать круглые скобки, которые не включаются в результирующую строку).
Параметр Topic является необязательным; при его
указании он определяет название подгруппы, в которую включается задание (название подгруппы
отображается в верхней строке окна задачника, а также
выводится в качестве заголовка подраздела в html-документе). Если
определяемая группа не предполагает деления на подгруппы, то параметр Topic не указывается; в этом случае
для всех заданий группы в верхней строке окна задачника отображается текст параметра
GroupDescription функции NewGroup с кратким описанием группы.
void Data(const char *comm)
template<typename T> void Data(const char *comm, T a);
template<typename T> void Data(const char *comm1, T a1, const char *comm2);
template<typename T1, typename T2> void Data(const char *comm1, T1 a1,
const char *comm2, T2 a2);
template<typename T1, typename T2> void Data(const char *comm1, T1 a1,
const char *comm2, T2 a2, const char *comm3);
template<typename T1, typename T2, typename T3> void Data(const char *comm1,
T1 a1, const char *comm2, T2 a2, const char *comm3, T3 a3);
template<typename Container> void Data(const Container &seq);
Функции этой группы позволяют задать исходные данные и комментарии к ним. Вызов каждой из функций
заполняет одну строку раздела исходных данных, за исключением вариантов с параметром-контейнером seq,
которые могут заполнять несколько строк (число строк зависит от
размера контейнера). В качестве параметров T могут использоваться данные базовых типов
bool, int, double, char, string, а в качестве параметра Container векторы vector<T>
или обертки над векторами типа Seq<T> из модуля linqcpp.h.
Если после комментария не указывается элемент данных, то соответствующая часть строки
отводится только под комментарий.
При указании одного элемента данных (или одного комментария) он центрируется. При указании двух
элементов (или элемента и отдельного комментария) первый элемент центрируется по левой половине окна задачника,
а второй по правой. При указании трех элементов (или двух элементов и отдельного комментария)
первый элемент центрируется в пределах левой половины окна задачника, второй центрируется в пределах
всего окна, а третий в пределах правой половины.
При выводе последовательности данных, представленной в виде вектора или объекта типа Seq, она занимает всю экранную строку
и выравнивается по ее центру, а если ширины строки недостаточно для отображения всех данных, они
переносятся на следующую строку и располагаются под элементами начальной строки.
При отображении числовых данных учитывается текущая настройка ширины области вывода,
а при отображении последовательности данных учитывается текущая настройка количества элементов
последовательности, отображаемых на одной экранной
строке. Эти настройки можно изменить с помощью функций SetWidth и SetSeqRowSize.
Более детальную настройку данных можно обеспечить, используя набор функций DataPos, описываемый далее.
При определении любого задания можно указывать не более 800 элементов исходных данных; если элементов более 800, то
в первой строке раздела с формулировкой задания выводится сообщение об ошибке.
void Res(const char *comm)
template<typename T> void Res(const char *comm, T a);
template<typename T> void Res(const char *comm1, T a1, const char *comm2);
template<typename T1, typename T2> void Res(const char *comm1, T1 a1,
const char *comm2, T2 a2);
template<typename T1, typename T2> void Res(const char *comm1, T1 a1,
const char *comm2, T2 a2, const char *comm3);
template<typename T1, typename T2, typename T3> void Res(const char *comm1,
T1 a1, const char *comm2, T2 a2, const char *comm3, T3 a3);
template<typename Container> void Res(const Container &seq);
Функции позволяют задать контрольные данные и комментарии к ним. Правила их использования полностью аналогичны
правилам для набора функций Data, задающих исходные данные (см. их описание, приведенное выше).
Контрольные данные используются при проверке правильности результатов, полученных учебной программой,
и отображаются в разделе «Пример верного решения» окна задачника. Создаваемую группу заданий можно
настроить таким образом, чтобы раздел с примером верного решения не отображался на экране (например, если
группа предназначена для использования при проведении контрольной работы); для этого достаточно в функции NewGroup
указать дополнительную опцию OptionHideExamples.
Указанные функции определяют также способ отображения результатов, найденных учебной программой
(результирующие данные вместе с комментариями отображаются в разделе «Полученные результаты»).
При определении любого задания можно указывать не более 800 элементов контрольных данных; если элементов более 800, то
в первой строке раздела с формулировкой задания выводится сообщение об ошибке.
void DataPos(const char *comm, int pos);
template<typename T> void DataPos(const char *comm, T a, int pos);
template<typename T> void DataPos(const char *comm, T a1, T a2, int pos);
template<typename T> void DataPos(const char *comm, T a1, T a2, T a3, int pos);
template<typename Container> void DataPos(const Container &seq, int pos)
Данные функции позволяют задать исходные данные и комментарии к ним в режиме явного позиционирования.
Значение параметра pos (0 < pos < 79) определяет позицию, в которой будут размещены данные в текущей строке.
Переход на новую строку при этом не осуществляется до тех пор, пока значение pos не станет меньше предыдущего значения,
или пока не будут вызваны функции с неявным позиционированием, которые были описаны выше.
Остальные параметры аналогичны параметрам функций Data. В качестве параметра шаблона Container, как и для функции Data,
должен использоваться вектор или класс Seq из модуля linqcpp.h. Тип данных контейнера должен соответствовать
базовым типам bool, int, double, char, string. После вызова функции DataPos
для последовательности данных всегда осуществляется переход на новую строку.
Функции DataPos могут оказаться полезными, если потребуется разместить в одной строке более трех элементов
исходных данных различных типов, а также если необходимо добавить комментарий в одной строке с последовательностью данных.
Кроме того, с помощью этих функций можно легко настраивать вывод пар и троек числовых данных вместе с общим комментарием
(варианты функции с параметрами a1, a2 или a1, a2, a3). Подчеркнем, что для отображения пар и троек можно использовать
только данные числовых типов int и double, для остальных типов (bool, char, string) этот вариант функции DataPos
использовать нельзя. Еще одна ситуация, при которой следует использовать функции DataPos,
настройка различной ширины области вывода для числовых данных, размещенных на одной и той же строке.
В качестве параметра pos можно указывать не только числовые значения, но и
константы PosLeft, PosCenter, PosRight и PosHide, описанные выше.
При размещении нескольких элементов данных с помощью функций DataPos на одной строке следует
использовать для всех этих элементов либо только явные числовые значения параметра pos, либо только
константы PosLeft, PosCenter, PosRight (в указанном порядке). Если после вызова функции DataPos с константой выполняется вызов
этой же функции с явным значением pos (или наоборот), то автоматически выполняется переход на новую экранную строку.
Константу PosHide можно указывать только для варианта функции DataPos с параметрами типа T; в этом случае
элементы данных a, a1, a2, a3 добавляются к заданию, но ни они, ни связанный с ними комментарий comm
не отображаются в окне задачника.
void ResPos(const char *comm, int pos);
template<typename T> void ResPos(const char *comm, T a, int pos);
template<typename T> void ResPos(const char *comm, T a1, T a2, int pos);
template<typename T> void ResPos(const char *comm, T a1, T a2, T a3, int pos);
template<typename Container> void ResPos(const Container &seq, int pos)
Функции добавляют контрольные данные и комментарии к ним в режиме явного позиционирования.
Правила их использования полностью аналогичны правилам использования функций DataPos.
Функции для включения в задание файлов
void DataFileInt(const char *FileName);
void DataFileDouble(const char *FileName);
void DataFileChar(const char *FileName);
void DataFileShortString(const char *FileName);
void DataFileText(const char *FileName, int LineCount = 5);
void ResFileInt(const char *FileName);
void ResFileDouble(const char *FileName);
void ResFileChar(const char *FileName);
void ResFileShortString(const char *FileName);
void ResFileText(const char *FileName, int LineCount = 5);
Функции, позволяющие добавлять к заданию файловые данные (двоичные файлы с содержимым типа int, double,
char, ShortString), а также текстовые файловые данные.
В задании можно использовать не более 5 исходных и не более 5 результирующих файлов.
Длина строк в двоичных строковых файлах (с элементами типа ShortString) и в текстовых файлах не должна превосходить 70.
Файловые данные с текстовой информацией должны храниться в однобайтной кодировке.
В каждом из разделов окна задачника, содержащих
файловые данные, должно определяться не более 5 строк с файловыми данными,
если считать, что на двоичный файл отводится одна строка,
а на текстовый количество строк, равное значению
параметра LineCount (параметр LineCount может принимать значения от 0 до 5; если при этом число строк в
разделе будет превышать 5, то значение параметра LineCount автоматически уменьшается).
Заметим, что несмотря на указанное выше формальное ограничение количества строк
(обусловленное особенностями базового варианта конструктора PT4TaskMaker),
фактически при отображении двоичных файлов будет использовано столько экранных строк,
сколько требуется для вывода всех их элементов, а содержимое текстовых файлов может
отображаться в двух вариантах: сокращенном (на LineCount строках) и полном.
Если LineCount равно 0, то содержимое текстового файла добавляется к данным, но не отображается в окне задачника.
Для элементов строковых двоичных файлов необходимо использовать особый тип ShortString.
При формировании строковых двоичных файлов может оказаться полезной вспомогательная функция ConvertToShortString.
При отображении числовых файловых данных учитывается текущая настройка ширины области вывода,
при отображении данных из двоичных файлов учитывается текущая настройка количества элементов
двоичных файлов, отображаемых на одной экранной строке.
Эти настройки можно изменить с помощью функций SetWidth и SetFileRowSize.
В случае текстовых файлов на каждой экранной строке отображается ровно одна строка текстового файла.
Как функции группы DataFile, так и функции группы ResFile, должны вызываться после формирования и закрытия
соответствующих исходных или контрольных файлов. Необходимо учитывать, что сразу после вызова функции группы ResFile
указанный в ней контрольный файл автоматически удаляется.
void ConvertToShortString(string s, char[256] buf);
Конвертирует обычную строку string в короткую строку типа ShortString и записывает результат в буфер buf.
Данная функция может оказаться полезной при формировании строковых двоичных файлов, включаемых в задание.
Функции, обеспечивающие дополнительную настройку задания
void SetTestCount(int n);
Задает количество тестов n (от 2 до 9), при успешном прохождении которых
задание считается выполненным. По умолчанию количество тестов полагается равным 5.
void SetRequiredDataCount(int n);
Задает количество n начальных элементов из набора исходных данных, которые достаточно
ввести для правильного решения задачи. По умолчанию равно общему количеству всех
исходных данных (данные, содержащиеся в файлах, не учитываются).
Функции, обеспечивающие дополнительную настройку отображения данных в окне задачника
void SetWidth(int n);
Задает минимальную ширину n поля вывода для числовых данных (числа выравниваются
по правому краю поля вывода, т. е. при необходимости дополняются слева пробелами).
Если параметр n не лежит в диапазоне 0..10, то вызов функции игнорируется. По
умолчанию минимальная ширина поля вывода полагается равной 0; в этом случае фактическая ширина
поля вывода определяется значениями исходных и контрольных числовых данных. Действие функции
продолжается до ее очередного вызова. Для нового задания восстанавливается значение
по умолчанию. Данная настройка влияет не только на отдельные элементы данных, но также
и на представление элементов числовых последовательностей и двоичных числовых файлов.
void SetPrecision(int n);
Задает количество n отображаемых дробных знаков для вещественных чисел. По
умолчанию n = 2. При положительных n числа отображаются в
формате с фиксированной точкой и n дробными знаками.
Если n = 0, то числа отображаются в
экспоненциальном формате с 6 дробными знаками. При отрицательных n числа отображаются в
экспоненциальном формате с |n| дробными знаками. Если |n| > 10, то вызов функции
игнорируется. Действие функции продолжается до ее очередного вызова. Для нового
задания восстанавливается значение по умолчанию. Данная настройка влияет не только
на отдельные элементы данных, но также
и на представление элементов числовых последовательностей и двоичных числовых файлов.
void SetSeqRowSize(int n);
Задает количество n элементов последовательности, отображаемых на одной экранной строке.
По умолчанию n = 0, это означает, что отображается максимально возможное количество элементов.
Действие функции продолжается до ее очередного вызова. Для нового
задания восстанавливается значение по умолчанию.
Если параметр n является отрицательным или его значение больше максимально возможного количества элементов,
которое можно отобразить на одной строке, то вызов функции игнорируется.
void SetFileRowSize(int n);
Задает количество n элементов двоичного файла, отображаемых на одной экранной строке.
По умолчанию n = 0, это означает, что отображается максимально возможное количество элементов.
Действие функции продолжается до ее очередного вызова. Для нового
задания восстанавливается значение по умолчанию.
Если параметр n является отрицательным или его значение больше максимально возможного количества элементов,
которое можно отобразить на одной строке, то вызов функции игнорируется.
Функции для определения текущего состояния задачника и выполняемого задания
int CurrentTest();
Возвращает номер текущего теста (тесты нумеруются от 1). В случае демонстрационного
запуска возвращает значение 0. Дает возможность генерировать особые варианты тестов на
определенном этапе тестирования программы. Подробный пример использования данной функции
приведен в ее описании для базового конструктора PT4TaskMaker.
int CurrentLanguage();
Возвращает целочисленную константу, соответствующую языку программирования, на который в данный момент настроен задачник.
Список констант приведен в разделе, посвященном базовому конструктора PT4TaskMaker.
В этом же разделе, при описании одноименной функции CurrentLanguage,
приводится пример ее использования для проверки того, что текущий язык программирования относится к определенной группе языков.
Как правило, данная функция вызывается в начале функции inittaskgroup, если разрабатываемая группа заданий
предусмотрена только для определенного набора языков. В этом случае, если текущий язык не входит в требуемый набор,
следует выполнить немедленный выход из функции inittaskgroup (без вызова функции NewGroup), в результате чего
данная группа не будет включена в набор групп заданий для текущего языка.
string CurrentLocale();
Возвращает строку, соответствующую текущей локали, т. е. текущему языку интерфейса, используемому
в задачнике. Возможными возвращаемыми значениями функции CurrentLocale являются string("ru")
(русский вариант задачника) и string("en") (английский вариант).
С помощью данной функции (и связанной с ней функции Loc) можно разрабатывать комбинированные динамические библиотеки, в которых формулировки заданий
и комментарии к данным (а возможно, и сами данные) зависят от текущей локали. Однако можно разрабатывать и варианты
динамических библиотек, ориентированных на конкретную локаль (в именах таких библиотек используются суффиксы «_en»
и «_ru»). Программа PT4GroupCreator позволяет создавать заготовки групп любого из описанных видов
(как комбинированные, так и ориентированные на конкретную локаль).
const char *Loc(const char *EnText, const char *RuText);
Проверяет текущую локаль задачника и в случае английской локали возвращает EnText, иначе возвращает RuText.
Данную функцию удобно использовать при разработке комбинированных динамических библиотек
для быстрого выбора комментария, связанного с различными локалями.
string CurrentVersion();
Возвращает номер текущей версии задачника в виде строки числа формата "d.dd" .
Например, для версии 4.25 возвращается строка "4.25" .
Как правило, данная функция вызывается в начале функции inittaskgroup, если разрабатываемая группа заданий
предусмотрена только для определенных версий задачника. В этом случае, если текущая версия имеет недопустимый номер,
следует выполнить немедленный выход из функции inittaskgroup (без вызова функции NewGroup), в результате чего
данная группа не будет включена в набор групп заданий для данной версии задачника.
Функции для получения образцов слов и предложений и генерации случайных числовых и текстовых данных
vector<string> Words();
vector<string> EnWords();
vector<string> Sentences();
vector<string> EnSentences();
vector<string> Texts();
vector<string> EnTexts();
Функции возвращают векторы слов, предложений и многострочных текстов, которые
можно использовать в качестве исходных данных. Варианты функций, в именах которых
присутствует текст En, всегда возвращают данные на английском языке, варианты без текста En
возвращают данные на английском языке, если задачник настроен на английскую локаль, и данные на русском языке,
если задачник настроен на русскую локаль.
Строки в многострочных текстах разделяются символом '\n',
в конце текста символ '\n' отсутствует. Длина предложений, а также строк в
многострочных текстах не превосходит 70 символов.
В наборы входят 116 слов, 61 предложение и 85 многострочных текстов.
string RandomWord();
string RandomEnWord();
string RandomSentence();
string RandomEnSentence();
string RandomText();
string RandomEnText();
Функции возвращают случайный элемент из стандартного набора слов, предложений и
многострочных текстов. Варианты функций, в именах которых присутствует текст En,
всегда возвращают данные на английском языке, варианты без текста En возвращают данные на английском языке,
если задачник настроен на английскую локаль, и данные на русском языке,
если задачник настроен на русскую локаль.
Строки в многострочных текстах разделяются символом '\n', в конце текста символ '\n' отсутствует.
Длина предложений, а также строк в многострочных текстах не
превосходит 70 символов.
int Random(int M, int N);
double Random();
double Random(double A, double B);
double Random1(double A, double B);
double Random2(double A, double B);
string RandomName(int len);
Дополнительные функции генерации случайных данных. Функция RandomName
генерирует случайную строку длины Len, состоящую из цифр и строчных (маленьких) латинских
букв. В функции Random(M, N) (с параметрами целого типа) границы диапазона M..N включаются в возможный набор
значений, в функции Random(A, B) (с параметрами вещественного типа) возвращается вещественное число из полуинтервала
[A, B). Если второй параметр функции Random меньше или равен первому, то функция
возвращает значение первого параметра. Функция Random без параметров возращает
случайное вещественное значение из полуинтервала [0,1).
Функции Random1 и Random2 являются
вариантами функции Random для вещественных параметров, которые
генерируют случайное вещественное число на промежутке [A, B)
с одним или, соответственно, двумя дробными знаками
и случайной добавкой порядка 1.0e-7.
Если промежуток [A, B) пуст, то эти функции возвращают A, округленное
до одного или двух дробных знаков и снабженное добавкой порядка 1.0e-7.
Функции Random1 и Random2 позволяют получать вещественные числа, которые
отображаются в окне задачника практически без потерь значащих цифр, что в ряде
случаев позволяет избежать проблем, связанных с округлением. Например,
если задание состоит в том, чтобы найти сумму двух вещественных чисел,
и для его реализации используется «обычная» функция Random, то
для исходных чисел A = 2.3441... и B = 3.4242...
суммой будет число A + B = 5.768... .
В результате округления до двух знаков в окне задачника эти числа будут
выведены в следующем виде:
A = 2.34
B = 3.42
A + B = 5.77
Такой результат может смутить внимательных учащихся, если они не знают,
что при отображении вещественных данных задачник выполняет их округление.
Использование функций Random1 и Random2 позволяет избежать подобных проблем.
При этом небольшая случайная добавка порядка 1.0e-7 по-прежнему обеспечивает уникальность
генерируемых чисел (в частности, в наборе чисел всегда будет единственный минимальный
и единственный максимальный элемент).
Функции для разработки заданий по параллельному MPI-программированию
Функции, описанные в настоящем разделе, связаны с разработкой заданий по параллельному программированию.
Подробное описание аналогичных функций для базового конструктора PT4TaskMaker приводится в соответствующем
разделе описания задачника Programming Taskbook for MPI-2.
void NewTask(const char *Topic, const char *TaskText, int *ProcessCount);
Перегруженный вариант функции NewTask, предназначенный для инициализации задания по параллельному
программированию. От других вариантов функции NewTask отличается
наличием параметра ProcessCount, задающего количество процессов параллельного приложения.
Следует обратить внимание на то, что этот параметр передается в виде указателя и является входным и выходным.
Особенности, связанные с параметром ProcessCount, описаны в разделе, посвященном
соответствующему варианту функции CreateTask базового конструктора PT4TaskMaker.
void SetProcess(int ProcessRank);
Данная функция устанавливает в качестве текущего
процесса параллельного приложения процесс ранга ProcessRank. Все
числовые исходные и контрольные данные связываются с текущим
процессом. До первого вызова данной процедуры текущим процессом
считается процесс ранга 0. Функцию можно вызывать несколько раз с
одним и тем же параметром (например, первый раз процесс делается
текущим при определении связанных с ним исходных данных, а второй раз
при определении его контрольных данных).
Параметр ProcessRank должен принимать значения в диапазоне от 0
до N 1, где N количество процессов,
возвращаемое параметром ProcessCount функции NewTask.
Отладочные средства конструктора
void ShowInfo(const string &info);
Функция ShowInfo выводит на экран всплывающее окно с текстом info. Она предназначена
для вывода отладочной информации в процессе разработки новых заданий.
Структура основного файла проекта для библиотеки, определяющей группу заданий
Ниже приводится пример основного файла проекта для динамической библиотеки с группой заданий
на русском языке (обратите внимание на вид первого параметра функции NewGroup, в котором необходимо
указать как английское, так и русское описание группы).
#include "pt4taskmakerX.h" // подключение заголовочного файла конструктора
DefineGroupName;
// макрос для получения имени группы из названия исходного cpp-файла
void __stdcall inittaskgroup() // функция инициализации группы
{
pt::NewGroup("Examples of various tasks|Примеры различных задач",
"М. Э. Абрамян, 2024", "qwqfsdf13dfttd");
}
DefineTask(Task1) // макрос, определяющий задание
{
pt::NewTask(R"(Формулировка задания (строка 1)
Формулировка задания (строка 2)
\PНовый абзац формулировки задания (строка 3))"
);
int a = pt::Random(1, 9);
pt::Data("Пример входных данных: a = ", a);
pt::Res("Пример результирующих данных: 2 * a = ", 2 * a);
pt::SetTestCount(5);
}
DefineTask(Task2) { // функция может импортировать задание из имеющейся группы
pt::UseTask("Begin", 3);
}
// Здесь могут располагаться другие задания,
// определенные с помощью макроса DefineTask.
// Имена функций с заданиями могут быть любыми,
// но должны соответствовать правилам именования функций в языке C++.
// Если при выполнении функции с заданием возникает исключение,
// то в окне задачника в начале раздела с формулировкой задания
// выводится имя исключения и строка с его описанием.
// Порядок добавления задания в группу
// соответствует порядку его определения в коде.
При разработке группы заданий для комбинированной локали (т. е. для комбинации русской и английской локали)
в основном файле проекта необходимо анализировать текущую локаль задачника. Проще всего это сделать,
используя функцию Loc:
#include "pt4taskmakerX.h" // подключение заголовочного файла конструктора
DefineGroupName;
// макрос для получения имени группы из названия исходного cpp-файла
void __stdcall inittaskgroup() // функция инициализации группы
{
pt::NewGroup("Examples of various tasks|Примеры различных задач",
pt::Loc("M. E. Abramyan, 2024", "М. Э. Абрамян, 2024"), "qwqfsdf13dfttd");
}
DefineTask(Task1) // макрос, определяющий задание
{
pt::NewTask(pt::Loc(
R"(\STask name.\s Task formulation (line 1)
Task formulation (line 2)
\PNew paragraph of the task formulation (line 3))",
R"(\SНазвание задания.\s Формулировка задания (строка 1)
Формулировка задания (строка 2)
\PНовый абзац формулировки задания (строка 3))"));
int a = pt::Random(1, 9);
pt::Data(pt::Loc("Sample input data: a = ",
"Пример входных данных: a = "), a);
pt::Res(pt::Loc("Sample result: 2 * a = ",
"Пример результирующих данных: 2 * a = "), 2 * a);
pt::SetTestCount(5);
}
DefineTask(Task2) { // функция может импортировать задание из имеющейся группы
pt::UseTask("Begin", 3);
}
// Здесь могут располагаться другие задания,
// определенные с помощью макроса DefineTask.
Обработка последовательностей в стиле LINQ
В конструктор PT4TaskMakerX включен вспомогательный заголовочный файл lingcpp.h,
позволяющий обрабатывать наборы данных, используя функции, аналогичные запросам технологии LINQ для платформы .NET.
В ряде случаев применение этих функций позволяет упростить обработку наборов данных, хотя, разумеется,
аналогичную обработку можно выполнять и стандартными средствами языка C++ (в частности, с помощью алгоритмов
библиотеки STL).
Ниже приводятся все виды запросов LINQ, реализованные в файле lingcpp.h:
// Фильтрует элементы последовательности согласно условию.
template <typename Container, typename Condition>
auto Where(const Container& container, Condition condition);
// Применяет функцию к каждому элементу последовательности,
// возвращая новую последовательность.
template<typename Container, typename Selector>
auto Select(const Container& container, Selector selector);
// Сортирует элементы последовательности по заданному компаратору.
// При сортировке по набору ключей должна вызываться несколько раз,
// причем более приоритетные ключи должны быть указаны в последнюю очередь.
// Примечание: данная функция отличается от набора функций LINQ OrderBy и ThenBy;
// ее компаратор должен возвращать не ключ для каждого элемента, а логическое
// значение - результат сравнения двух ключей неравенством "<" (как в алгоритме
// sort библиотеки STL).
template<typename Container, typename Comparator>
auto SortWith(const Container& container, Comparator comparator);
// Группирует элементы последовательности по ключу,
// возвращая вектор пар "ключ-группа".
template<typename Container, typename KeySelector>
auto GroupBy(const Container& container, KeySelector keySelector);
// Возвращает новую последовательность
// с заданным количеством элементов из начала исходной.
template<typename Container>
auto Take(const Container& container, size_t count);
// Объединяет две последовательности поэлементно с помощью заданной функции.
template<typename Container1, typename Container2, typename ResultSelector>
auto Zip(const Container1& container1, const Container2& container2,
ResultSelector selector);
// Применяет функцию к каждому элементу последовательности,
// получая для каждого элемента новую последовательность,
// после чего возвращает "плоскую" последовательность,
// содержащую все элементы всех полученных последовательностей.
template<typename Container, typename Selector>
auto SelectMany(const Container& container, Selector selector);
// Применяет действие к каждому элементу последовательности.
template<typename Container, typename Action>
auto ForEach(const Container& container, Action action);
// Возвращает последовательность с одним элементом,
// если исходная последовательность пуста.
template<typename Container>
auto DefaultIfEmpty(const Container& container);
// Возвращает последовательность с одним элементом,
// если исходная последовательность пуста.
template<typename Container>
auto DefaultIfEmpty(const Container& container,
typename Container::value_type default_value);
// Возвращает первый элемент последовательности
// или значение по умолчанию, если последовательность пуста.
template<typename Container>
auto FirstOrDefault(const Container& container);
// Возвращает первый элемент последовательности, удовлетворяющий условию
// condition, или значение по умолчанию, если последовательность
// не содержит требуемых элементов.
template<typename Container, typename Condition>
auto FirstOrDefault(const Container& container, Condition condition);
// Возвращает последний элемент последовательности
// или значение по умолчанию, если последовательность пуста.
template<typename Container>
auto LastOrDefault(const Container& container);
// Возвращает последний элемент последовательности, удовлетворяющий условию
// condition, или значение по умолчанию, если последовательность
// не содержит требуемых элементов.
template<typename Container, typename Condition>
auto LastOrDefault(const Container& container, Condition condition);
// Возвращает количество элементов последовательности
// (как целое число типа int)
template<typename Container>
int Count(const Container& container);
// Возвращает количество элементов последовательности,
// удовлетворяющих условию (как целое число типа int)
template<typename Container, typename Condition>
int Count(const Container& container, Condition condition);
// Возвращает новую последовательность, состоящую из результатов
// объединения элементов двух коллекций по заданным ключам
// (выполняется внутреннее объединение).
template<typename Container1, typename Container2, typename KeySelector1,
typename KeySelector2, typename ResultSelector>
auto Join(const Container1& container1, const Container2& container2,
KeySelector1 keySelector1, KeySelector2 keySelector2,
ResultSelector resultSelector);
// Выполняет левое внешнее объединение двух последовательностей
// по заданным ключам.
template<typename Container1, typename Container2, typename KeySelector1,
typename KeySelector2, typename ResultSelector>
auto GroupJoin(const Container1& container1, const Container2& container2,
KeySelector1 keySelector1, KeySelector2 keySelector2,
ResultSelector resultSelector);
// Возвращает последовательность, содержащую элементы
// исходной последовательности в обратном порядке.
template<typename Container>
auto Reverse(const Container& container);
// Вычисляет среднее значение элементов в последовательности.
template<typename Container>
double Average(const Container& numbers);
// Вычисляет сумму всех элементов в последовательности.
template<typename Container>
typename Container::value_type Sum(const Container& numbers);
// Проверяет, удовлетворяют ли все элементы последовательности
// заданному условию.
template<typename Container, typename Condition>
bool All(const Container& container, Condition condition);
// Проверяет, удовлетворяет ли хотя бы один элемент последовательности
// заданному условию.
template<typename Container, typename Condition>
bool Any(const Container& container, Condition condition);
// Находит минимальный элемент в последовательности.
template<typename Container>
auto Min(const Container& container);
// Находит максимальный элемент в последовательности.
template<typename Container>
auto Max(const Container& container);
Первым параметром всех запросов является обрабатываемая последовательность,
имеющая тип vector<T> или Seq<T>. Результатом любого запроса, возвращающего
последовательность, является объект типа Seq<TRes> (где тип TRes может совпадать с типом T,
а может и отличаться от него).
Параметры типа Condition, Comparator, Action, Selector/KeySelector/ResultSelector
представляют собой лямбда-выражения. Ниже приведены образцы таких выражений
в предположении, что типом исходной последовательности является T (если в запросе используются
две последовательности, то типом второй последовательности является T2).
Следует заметить, что параметры лямбда-выражений можно описывать двумя способами:
либо как T e , либо как const T &e (второй способ является более эффективным).
// Condition (запросы Where, All, Any, FirstOrDefault, LastOrDefault, Count):
[](T e){return true, если элемент e удовлетворяет условию, иначе false;}
// Comparator (запрос SortWith):
[](T e1, T e2){return true, если элемент e1 меньше e2, иначе false;}
// Action (запрос ForEach):
[](T e){оператор;}
// Selector (запрос Select):
[](T e){return значение типа TRes;}
// Selector (запрос SelectMany):
[](T e){return последовательность с элементами типа TRes;}
// KeySelector (запросы GroupBy, Join, GroupJoin):
[](T e){return ключ, соответствующий элементу e;}
// ResultSelector (запросы Zip, Join):
[](T e1, T2 e2){return значение типа TRes;}
// ResultSelector (запрос GroupJoin):
[](T e1, vector<T2> e2){return значение типа TRes;}
Класс Seq<T> определен в том же файле linqcpp.h и представляет собой «обертку» вокруг
вектора vector<T>, для которой доступны все возможности исходного вектора.
Важной особенностью класса Seq является возможность применения к его
объектам модификаций перечисленных выше запросов, в которых вместо первого параметра
указывается объект Seq в точечной нотации. Например,
вместо запроса Where(seq, condition)
можно использовать выражение seq.Where(condition) .
Поскольку при этом возвращается объект Seq, к нему можно применять новый запрос,
формируя таким образом цепочку запросов.
Объекты типа Seq<T> могут быть неявно преобразованы к типу vector<T>,
поэтому их можно использовать во всех функциях, принимающих объекты vector<T>
в качестве параметров.
Для генерации последовательностей типа Seq<T> предусмотрен набор функций SeqFill и SeqGen:
// Возвращает последовательность из n элементов (n >= 0), для которой
// все элементы равны значению v
template<typename T>
Seq<T> SeqFill(int n, T v);
// Возвращает последовательность из n элементов (n > 0), для которой
// элементы определяются функцией elem с параметром-индексом: e_i = elem(i)
template<typename Func>
auto SeqGen(int n, Func elem) -> Seq<decltype(elem(0))>;
// Возвращает последовательность из n элементов (n > 0), для которой
// начальный элемент определяется параметром first, а остальные
// вычисляются рекурсивно с помощью функции next: e = next(e_prev)
template<typename T, typename Func>
Seq<T> SeqGen(int n, T first, Func next);
// Возвращает последовательность из n элементов (n > 0), для которой
// два начальных элемента определяются параметрами first и second,
// а остальные вычисляются рекурсивно с помощью функции next:
// e = next(e_prevprev, e_prev)
template<typename T, typename Func>
Seq<T> SeqGen(int n, T first, T second, Func next);
Кроме того, в классе Seq<T> определены три дополнительные функции:
// Возвращает новую последовательность, состоящую из элементов исходной
// последовательности и последовательности seq2 (в указанном порядке)
Seq<T> Concat(Seq<T> seq2)
// Находит индекс первого элемента последовательности, равного v.
// Поиск начинается с элемента с индексом start; проверяются count элементов
// (или все оставшиеся элементы, если их количество меньше, чем count).
// Если start < 0, то параметр start полагается равным 0,
// если count < 0, то параметр count полагается равным длине последовательности.
// Если требуемые элементы отсутствуют, то возвращается -1.
int IndexOf(T v, int start = -1, int count = -1)
// Находит индекс последнего элемента последовательности, равного v.
// Поиск начинается с элемента с индексом start; проверяются count элементов
// (или все начальные элементы, если их количество меньше, чем count).
// Если start < 0, то параметр start полагается равным длине последовательности минус 1,
// если count < 0, то параметр count полагается равным длине последовательности.
// Если требуемые элементы отсутствуют, то возвращается -1.
int LastIndexOf(T v, int start = -1, int count = -1)
Для ознакомления с особенностями сложных запросов (таких как Join, GroupJoin и GroupBy)
удобно использовать контекстные подсказки редактора Visual Studio Code.
Например, допустимым оператором является следующий:
auto gr = GroupBy(pt::EnWords(), [](string e){ return e[0]; });
Он выполняет группировку всех английских слов, возвращаемых функцией EnWords
по их первым символам. Если навести курсор на переменную gr в редакторе Visual Studio Code,
то появится подсказка Seq(std::pair<char, std::vector<std::string>>> gr ,
которая позволяет определить тип результата группировки: это последовательность Seq,
состоящая из пар значений, причем первым элементом пары является символ (ключ группировки),
а вторым вектор строк, начинающихся с данного символа.
Заметим, что результат выполненной группировки можно наглядно отобразить в окне задачника
(в разделе результатов), если использовать следующий фрагмент кода:
for (auto e : gr)
{
pt::ResPos("Key =", e.first, 1);
pt::ResPos("Group:", 10);
pt::ResPos(e.second, 17);
}
Программа PTGroupCreator для генерации заготовок новых групп заданий
Создавать новые группы заданий с использованием конструктора PT4TaskMakerX
проще всего с помощью специальной программы PTGroupCreator.
На странице, посвященной данной программе, можно ознакомиться с ее подробным описанием
и примером реализации демонстрационной группы заданий DemoCppX.
|