Автор I_love_Hoang_Yen, история, 9 лет назад, перевод, По-русски

Если вы написали несколько задач и подготовили для них тесты, вероятно, вы будете крайне неприятно себя чувствовать, если какие-то из тестов окажутся некорректными (в том смысле, что они не будут согласованы с условием задачи): значение какой-то величины будет больше допустимой верхней границы для нее, граф не будет удовлетворять требованиям связности или же не будет являться деревом... Это естественно, что вы будете себя так чувствовать. Даже опытные авторы задач не застрахованы от ошибок (это случается даже на самых престижных соревнованиях: пример тому — финал чемпионата мира ACM ICPC 2007 г.)

Настоятельно рекомендуется писать специальную программу (называемую валидатором), чтобы формально проверить каждый тест на соответствие всем требованиям условия задачи. Валидаторы обязательны для задач, которые готовятся для Codeforces. Polygon имеет встроенную поддержку валидаторов.

Написать валидатор с помощью testlib.h на самом деле очень легко.

Пример

Ниже приводится валидатор, который мог бы быть написан для задачи 100541A - Stock Market:

#include "testlib.h"

int main(int argc, char* argv[]) {
    registerValidation(argc, argv);
    int testCount = inf.readInt(1, 10, "testCount");
    inf.readEoln();
    
    for (int i = 0; i < testCount; i++) {
        setTestCase(i + 1);
        int n = inf.readInt(1, 100, "n");
        inf.readSpace();
        inf.readInt(1, 1'000'000, "w");
        inf.readEoln();

        inf.readInts(n, 1, 1000, "p");
        inf.readEoln();
    }

    inf.readEof();
}
Оригинальный валидатор, использующий более старые версии testlib.h

Самое замечательное в этом валидаторе то, что он очень простой, и в нем очень трудно написать что-то неправильно.

В репозитории Github можно найти другие примеры валидаторов.

Функции и методы

Первая строка вашего кода должна содержать вызов registerValidation(argc, argv): немного магии, и вы можете использовать необходимые методы. Большинство методов для валидатора являются методами входного потока inf, начинаются со слова read и именно выполняют чтение: перемещают указатель во входном потоке на следующую позицию после прочтения чего-либо. В процессе чтения обнаруживаются нарушения (входные данные не соответствуют тому, что вы пытаетесь прочитать: например, вы предпринимаете попытку прочитать целое число, а во входных данных встречается строка) и выбрасывается ошибка. Обычно название метода имеет вид inf.readT[s], где T равно Int, Long, Double, Token, Line, Space, Eoln, Eof и т. п. в зависимости от типа данных, который нужно прочитать, а суффикс s необходим для считывания массива однотипных данных. Полный список таких функций приведен в конце этого поста. Кроме того, доступны следующие функции:

Метод / функция Что делает
void registerValidation(argc, argv) Эта функция должна быть вызвана в начале вашего кода, чтобы использовать валидатор. После вызова этой функции вы получаете доступ к входному потоку посредством переменной inf.
void setTestCase(int T) Для задач, имеющих несколько наборов входных данных, позволяет при нарушении условий валидации автоматически выводить номер набора входных данных T, где произошла ошибка.
void unsetTestCase() Отменяет вывод номера набора входных данных при ошибке.
string validator.testcase() Возвращает название набора тестов, для которого вызван валидатор. При запуске из командной строки используйте параметр --testset testcase_name для установки названия.
string validator.group() Возвращает название группы тестов, для которой вызван валидатор. При запуске из командной строки используйте параметр --group group_name для установки группы.
void ensure(bool cond) При невыполнении cond прерывает выполнение валидатора с ошибкой.
void ensuref(bool cond, char *format, ...) При невыполнении cond прерывает выполнение валидатора с ошибкой, дополнительно указывая комментарий (см. ниже).

Параметр variableName

Рекомендуется дополнительно передавать последним параметром строковое значение variableName в методы read*, чтобы сделать сообщение об ошибке более удобным для чтения. Т. е. предпочтительнее использовать inf.readInt(1, 100, "n") вместо inf.readInt(1, 100). При возникновении ошибки в первом случае будет выводиться сообщение вида FAIL Integer parameter [name=n] equals to 0, violates the range [1, 100].

Использование ensure/ensuref

Чтобы проверить некоторые требования (например, то, что граф не содержит петель, т.е. что xi ≠ yi), используйте ensuref(x_i != y_i, "Graph can't contain loops"). Допускается использование спецификаторов формата языка C, подобных ensuref(s.length() % 2 == 0, "String 's' should have even length, but s.length()=%d", int(s.length())). Также вы можете использовать более простую форму ensure(x > y), в этом случае будет печататься нарушенное условие, если оно не выполняется: FAIL Condition failed: "x > y".

Замечания:

  • Валидатор строг. Он проверяет корректное расположение пробелов. Например, последовательность вызовов вида прочесть число, прочесть пробел, прочесть число гарантирует наличие ровно одного пробела между числами; в противном случае валидатор сообщит об ошибке.
  • Некоторые методы частично поддерживают синтаксис регулярных выражений. Конечно, это не полноценные регулярные выражения, которые вы можете использовать во многих языках программирования. Это очень простая версия, в которой поддерживается следующее:
    • Множество символов: например, [a-z] — любые строчные латинские буквы, [^a-z] — любые символы за исключением строчных латинских букв.
    • Диапазон, например, шаблон [a-z]{1,5} описывает строки длиной от 1 до 5 символов, содержащие только строчные латинские буквы.
    • Оператор Или, например, шаблон mike|john — это или строка mike, или строка john.
    • Необязательные символы, например, шаблон -?[1-9][0-9]{0,3} допускает ненулевые целые числа от -9999 до 9999 (обратите внимание на необязательный знак "минус").
    • Повторения, например, шаблон [0-9]* допускает последовательности (как пустые, так и непустые) цифр, а шаблон [0-9]+ только непустые последовательности цифр.
  • Также заметим, что при распознавании регулярных выражений используется очень простой жадный алгоритм. Например, шаблон [0-9]?1 не допускает 1 в силу жадного поведения распознавателя.

Ниже представлен полный список функций и методов входного потока (inf.<method>).

Метод / функция Что делает
char readChar() Этот метод возвращает текущий символ и перемещает указатель на один символ вперед.
char readChar(char c) Аналогичен readChar(), но обеспечивает проверку, что прочитанный символ именно c.
char readSpace() Аналогичен readChar(' ').
void unreadChar(char c) Возвращает символ c во входной поток.
string readToken(),
string readWord()
Читает и возвращает очередную лексему (токен).
string readToken(string regex),
string readWord(string regex)
Аналогичен readToken(), но выполняет проверку соответствия лексемы (токена) указанному регулярному выражению regex.
vector<string> readTokens(int n, string regex),
vector<string> readWords(int n, string regex)
Читают и возвращают n лексем (токенов) через пробел, каждый токен должен соответствовать регулярному выражению regex.
int readInt(),
int readInteger()
Читает и возвращает целое число (тип int как в Java, так и в C/C++)
int readInt(int L, int R),
int readInteger(L, R)
Аналогичны readInt(), но выполняет проверку, что значение находится в диапазоне [L, R] (включительно)
vector<int> readInts(int n, int L, int R),
vector<int> readIntegers(int n, int L, int R)
Читает n целых чисел (тип int как в Java, так и в C/C++) через пробел, выполняет проверку, что значения находятся в диапазоне [L,  R] (включительно) и возвращает vector
long long readLong() Читает и возвращает длинное целое (long long в C/C++ и long в Java)
long long readLong(long long L, long long R) Аналогичен readLong(), но выполняет проверку, что значение находится в диапазоне [L, R] (включительно)
vector<long long> readLongs(int n, long long L, long long R) Читает n длинных целых чисел (long long в C/C++ и long в Java) через пробел, выполняет проверку, что значения находятся в диапазоне [L,  R] (включительно) и возвращает vector
double readReal(),
double readDouble()
Читают и возвращают вещественное число (double).
double readReal(double L, double R),
double readDouble(double L, double R)
Аналогичны readReal(), readDouble(), но выполняют проверку, что значение находится в диапазоне [L, R].
double readStrictReal(double L, double R, int minPrecision, int maxPrecision),
double readStrictDouble(double L, double R, int minPrecision, int maxPrecision)
Аналогичны readReal(L, R), readDouble(L, R), но выполняют дополнительную проверку, что количество цифр после десятичной точки находится в диапазоне [minPrecision, maxPrecision]. Экспоненциальная запись числа или другие нестандартные формы записи не допускаются.
vector<double> readReals(int n, double L, double R),
vector<double> readDoubles(int n, double L, double R),
vector<double> readStrictReals(int n, double L, double R, int minPrecision, int maxPrecision),
vector<double> readStrictDoubles(int n, double L, double R, int minPrecision, int maxPrecision)
Читает n вещественных чисел с соответствующими ограничениями через пробел и возвращает vector
string readString(),
string readLine()
Прочитывают строку, начиная с текущей позиции до EOLN. Перемещают указатель во входном потоке на первый символ следующей строки (если она существует).
string readString(string regex),
string readLine(string regex)
Аналогичны readString() and readLine(), но выполняют проверку, что строка соответствует указанному регулярному выражению regex.
vector<string> readStrings(int n, string regex),
vector<string> readLines(int n, string regex)
Читают и возвращают n строк, соответствующие регулярному выражению regex.
void readEoln() Читает EOLN или завершает работу с ошибкой. Заметим, что этот метод чудесным образом работает как для Windows, так и для Linux. В Windows он прочитывает #13#10, а в Linux #10.
void readEof() Читает EOF или завершает работу с ошибкой.

Ссылки: страница testlib.h на Github

  • Проголосовать: нравится
  • +182
  • Проголосовать: не нравится

»
9 лет назад, # |
  Проголосовать: нравится +12 Проголосовать: не нравится

Auto comment: topic has been updated by I_love_Hoang_Yen (previous revision, new revision, compare).

»
9 лет назад, # |
  Проголосовать: нравится +7 Проголосовать: не нравится

Auto comment: topic has been updated by MikeMirzayanov (previous revision, new revision, compare).

»
9 лет назад, # |
  Проголосовать: нравится +26 Проголосовать: не нравится

Auto comment: topic has been updated by elena (previous revision, new revision, compare).

»
9 лет назад, # |
  Проголосовать: нравится +37 Проголосовать: не нравится

Автокомментарий: текст был переведен пользователем elena (оригинальная версия, переведенная версия, сравнить).

»
9 лет назад, # |
  Проголосовать: нравится +7 Проголосовать: не нравится

Thank for your testlib.h It is taught a lot of things

»
9 лет назад, # |
  Проголосовать: нравится -82 Проголосовать: не нравится

Auto comment: topic has been updated by aitzhan.askar (previous revision, new revision, compare)

»
9 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Auto comment: topic has been updated by PrinceOfPersia (previous revision, new revision, compare).

»
9 лет назад, # |
  Проголосовать: нравится +5 Проголосовать: не нравится

What has happened during the ACM ICPC world finals 2007? Was there something wrong with the problem test cases?

»
9 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

You may want replace inf.readChar(' ') by inf.toSpace() in the example (which is more clear in my opinion)

»
9 лет назад, # |
  Проголосовать: нравится +12 Проголосовать: не нравится

Автокомментарий: текст был обновлен пользователем riadwaw (предыдущая версия, новая версия, сравнить).

»
9 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Thanks for this great tutorial and for this great tool.

Although there might be a typo in the list of methods available: I think it should be: long long readLong(long long L, long long R)

instead of: long long readLong(int L, int R)

I hope this in not a bug in testLib.h as well...

»
8 лет назад, # |
  Проголосовать: нравится +5 Проголосовать: не нравится

Auto comment: topic has been updated by Zlobober (previous revision, new revision, compare).

»
6 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Auto comment: topic has been updated by arsijo (previous revision, new revision, compare).

»
5 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

For the regex feature which pattern can use to check strings that contains decimals rounded to 3 digits like "0.700","2.800","4.900"? I tried ([0-9]{1-10}).([0-9]{3}) but that didn't work.

  • »
    »
    5 лет назад, # ^ |
    Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

    Comma is used to separate the range limits. It's probably [0-9]{1,10}.[0-9]{3} (but it'll match something like 000.000). Maybe [1-9][0-9]*.[0-9]{3} or [1-9][0-9]{0,8}.[0-9]{3}? Or (0|[1-9][0-9]*).[0-9]{3}

  • »
    »
    5 лет назад, # ^ |
    Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

    you probably should also replace . with \. because . usually means any character (not sure about testlib)

  • »
    »
    5 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    Consider to use readStrictDouble.

»
5 лет назад, # |
Rev. 2   Проголосовать: нравится +5 Проголосовать: не нравится

Did I miss something or the return 0;(or any return statement) is missing at the end of the main function in the example?

»
5 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

How can I check my input data to EOF, for example if I have random input lines without 'n' describing size of input data?

**

Как мне проверить входящие данные до EOF (конца файла) если не задан их размер, например рандомное количество строк, без параметра 'n' определяющего их количество?

»
3 года назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

I get a structure warning in Polygon:

Structure warnings: Problem seems to have testcases, but validator doesn't call setTestCase()

I see that setTestCase is a latest feature in testlib.h, however, I cannot find any example validators using it. The three official examples on Polygon website either don't have multi-test inputs ('New Password'), or have a manual for-loop ('One-Way Reform') to capture them.

Could anyone explain how to use this latest feature in Polygon, for easier processing of multi-test single input files? (I believe)

  • »
    »
    3 года назад, # ^ |
      Проголосовать: нравится +8 Проголосовать: не нравится

    Hi. Look into the official repo https://github.com/MikeMirzayanov/testlib/ see validators/case-nval.cpp

    /**
     * Validates t (1 <= t <= 10) test cases.
     * The first line contains the integer between 1 and 10^4, inclusive.
     * The second line should contain a space-separated sequence of integers between -1000 and 1000, inclusive.
     * Also validates that the file ends with EOLN and EOF.
     */
    
    #include "testlib.h"
    
    using namespace std;
    
    int main(int argc, char* argv[]) {
        registerValidation(argc, argv);
    
        int testCaseCount = inf.readInt(1, 10, "t");
        inf.readEoln();
    
        for (int testCase = 1; testCase <= testCaseCount; testCase++) {
            setTestCase(testCase);
            int n = inf.readInt(1, 10000, "n");
            inf.readEoln();
            inf.readInts(n, -1000, 1000, "a");
            inf.readEoln();
        }
        
        inf.readEof();
    }
    
»
8 дней назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

May I ask that shouldn't ans.readLine("(.*?)", "answer") accept any kind of lines as the regex "(.*?)" literally accept any strings?