10. Solution Files

Polygon 2015. 8. 12. 00:38

문제의 input을 만드는 generator와, input이 합당한지 확인하는 validator를 만들었으니, 이제 input을 토대로 답을 내는 solution을 올릴 차례입니다. Solution files 탭에 가면 다음과 같은 화면이 우리를 반겨줍니다.



Add soultion을 누르면 자주 봐왔던 파일 업로드 창이 보이며 여기에 업로드할 때도 파일명에 사용할 수 있는 문자들은 다음과 같습니다.

  • 알파벳 소문자 및 대문자

  • 숫자

  • .(Period, 0x2E)

  • _(Underscore, 0x5F)

  • -(Hyphen, 0x2D)

이런 식으로 파일을 선택하고, Language를 선택해서 올리면 solution이 추가됩니다. 


밑에 있는 'Check solutions for compilability'는 solution이 컴파일 가능한지 확인하는 버튼입니다.

표에 있는 문구들의 의미를 하나씩 해석하면 다음과 같습니다.

  • Author: 해당 solution을 업로드한 사람입니다. 혼자 하면 자신의 id가 나타나겠지만 Polygon의 문제는 타인과 공유가 가능하다는 사실을 잊지 마세요.

  • Name: solution의 이름입니다. Rename을 통해서 이름을 바꿀 수 있습니다.

  • Language: solution의 언어입니다.

  • Length: solution의 크기(용량)입니다. 참고로 Codeforces의 상한은 64kB입니다.

  • Modified: 최근 수정 날짜를 의미합니다.

  • Type: soultion이 어떤 종류의 풀이인지를 의미합니다. 창을 보면 8가지 중 하나를 선택해야 합니다. 이 Type는 나중에 Invocation이라는 것을 할 때 매우 중요해집니다.

    • Main correct solution: 일명 'jury's solution'입니다. 기본적으로 '맞는 풀이' 중 하나이나, 이 풀이는 Codeforces의 Hack이나, Checker 등을 통한 test에 대해 답을 출력하는데 사용됩니다. Main correct solution은 한 문제에 하나만 있을 수 있습니다.

    • Correct: 그냥 맞는 풀이입니다.

    • Incorrect: 여러 가지로 틀린 풀이입니다. 이 경우 test에 따라서 Runtime Error가 날 수도 있고, Time Limit Exceeded가 날 수도 있고, Wrong answer가 날 수도 있고, 맞을 수도 있고......

    • Time limit exceeded: 알고리즘은 맞는데 문제가 요구하는 것에 비해 느린 풀이를 의미합니다.

    • Wrong answer: 답이 틀린 풀이입니다.

    • Presentation error: 이 경우는 whitespace 관련해서 output이 이상한 경우입니다. 예를 들면 정수 2개를 공백으로 구분해서 출력하라고 했는데 줄바꿈을 하여 출력한다든지......일부 Online judge에는 있는 것 같지만, 일단 Codeforces에는 Presentation error verdict가 현재로서는 없습니다. 사실 옛날 초기 Codeforces에는 있었습니다만, 이 링크를 보면 폐지했습니다. 뭐 굳이 쓰자면 checker에서 검증할 수도 있겠습니다만......추천하지는 않습니다.

    • Memory limit exceeded: 문제에서 주어진 메모리보다 더 많은 메모리를 사용한 것을 의미합니다. 예시로는 배열의 크기가 너무 크거나(이 때는 컴파일이 거부되는 경우도 있습니다), std 컨테이너에 원소를 너무 많이 집어넣거나 하는 경우가 있습니다.

    • Failed: 프로그램 실행 도중 에러가 발생해 시스템이 프로그램을 중단하는 경우입니다. 달리 말하자면 Runtime error입니다. Runtime error가 나는 경우는 대단히 많지만, competitive programming을 하다가 runtime error가 나는 경우는 대부분은 이런 경우들입니다.

      • Out-of-bounds, 특히 배열을 잘못 참조할 때
      • 0으로 나눌 때(Division by 0)
      • 재귀함수를 너무 많이 호출해서 스택이 터졌을 때(stack buffer overflow)
이런 식으로 코드를 올리면 시험도 해봐야겠죠? 이를 Invocations 또는 Stresses에서 진행 가능하지만, 아직은 할 수 없습니다. 제출한 코드가 내놓는 답(participant's solution)이 실제 답(jury's solution)과 일치하는지 확인해야 하기 때문입니다. 이는 Checker라는 프로그램이 담당합니다.


'Polygon' 카테고리의 다른 글

12. Stress  (0) 2015.08.18
11. Checker  (0) 2015.08.17
9. Validator  (1) 2015.08.11
8. Tests (2 - Generator with Freemarker)  (0) 2015.08.09
7. Tests (1 - Generator with testlib.h)  (0) 2015.08.02
Posted by Evenharder
,

9. Validator

Polygon 2015. 8. 11. 20:38

Validator는 지금까지 고생하면서 만든 input 파일이 올바른 양식(이는 whitespace도 포함합니다)을 지니고 있는지, 또 문제 input 조건을 어기는 test가 invalid한지 등을 판별하는 프로그램입니다. 이런 프로그램이 따로 있는 것은 기본적으로는 test가 말 그대로 valid한지 판별하는 목적도 있겠지만, Codeforces의 Hack 등을 걸러내는 것에도 목적이 있습니다.


Validator 역시 testlib.h를 사용하여 만드는 것이 강력하게 권장됩니다. 우선 Polygon의 Validator 탭에 들어가 봅시다.



각 항목을 설명하자면 다음과 같습니다.

  • Select: Files 탭에서 'Source Files'에 업로드된 파일들을 선택할 수 있습니다. 기본적으로는 저렇게 'No validator'라고 나와 있습니다.

  • Or upload: 아니면 사용자의 컴퓨터에서 업로드할 수 있습니다. 확인해보지는 않았지만, 이 떄 올리는 파일의 이름 역시 4. Statement의 그림 파일 올릴 때처럼 다음 조건을 만족해야 할 겁니다.

    • 알파벳 소문자 및 대문자

    • 숫자

    • .(Period, 0x2E)

    • _(Underscore, 0x5F)

    • -(Hyphen, 0x2D)

  • Language: validator의 언어입니다. 일반적으로 Autodetect(자동 감지)를 사용해도 전혀 무리가 없으며, 아니면 testlib.h 자체의 특성상 대부분 gcc.c++로 할 테지만, 그 외에도 다양한 언어로 짤 수는 있습니다. 하지만 다른 언어로 옮기면서 예상치 못한 버그가 나타날 수도 있으니, 그냥 c++로 짜는 것을 추천드립니다.
아무튼 validator를 올리는 과정 자체는 간단합니다. 이제 validator를 만드는 과정이 남아 있을 뿐입니다. 그다지 어렵지는 않으므로, 걱정하진 마세요.
validator를 만드면서 꼭 필요할 함수들을 소개하겠습니다. registerValidation 함수만 제외하면, inf.xxx의 형태로 사용이 가능합니다. 이 inf는 testlib.h 에 선언되있는 class 중 하나인 InStream이 전역 변수의 형태로 선언되어 있는 것이며, generator가 생성한 값이 여기에 저장됩니다.
  • void registerValidation(int argc, char* argv[]):
    validator 선두에 무조건적으로 호출해야 하는 함수입니다. 이 함수의 역할은 argv로 받은 인자들이 올바른지 판별하는 역할을 합니다. 참고로 validator 실행파일에 주어지는 인자는 Polygon이 알아서 잘 집어넣어 줍니다.

  • void skipBlanks():
    whitespace가 아닌 문자를 만날 때까지, 아니면 EoF(End of File, 파일의 끝)를 만날 때까지 포인터를 이동합니다. 이 포인터는 그냥 input을 저장하는 배열값을 가리키는 포인터라고 생각하면 되겠습니다.

  • char readChar():
    문자를 하나 읽으면서 포인터를 다음 문자로 넘깁니다. return값은 읽은 문자값입니다.

  • char readChar(char c):
    문자를 하나 읽으면서, 포인터를 다음 문자로 넘깁니다. return값은 읽은 문자값입니다. readchar()과의 차이점은 읽은 문자가 c와 같지 않을 경우 'Unexpected character 'x', but 'y' expected'의 형식으로 stderr로 출력합니다.

  • char readSpace():
    readChar(' ')과 동일합니다. ' '(SPACE)를 읽으며 포인터를 다음 문자로 넘기되, SPACE가 아니면 stderr로 해당 사실을 출력합니다.

  • void unreadChar(char c):
    포인터를 한 칸 앞으로 옮기면서, input 배열에(실제로는 std::string입니다만)그 자리에 c를 집어넣습니다. 당연한 소리지만, 포인터가 맨 앞에 있는데 이 함수를 호출하면 에러가 납니다.

  • std::string readToken(), std::string readWord():
    한 단어, 즉 whitespace 또는 EoF를 만나기 전까지의 모든 글자들을 std::string 형식으로 읽으며, 이를 return합니다. 이 경우 포인터는 그 whitespace 또는 EoF를 가리키게 됩니다.

  • std::string readToken(const std::string& ptrn, const std::string& variableName = ""): 구조적으로는 위의 함수와 똑같지만, 2가지 차이점이 있습니다. 첫째는 읽은 문자열이 ptrn로 주어진 Regex 형태와 일치해야 하며, 둘째로는 이 문자열의 '이름'을 variableName을 통해 명명할 수 있다는 것입니다. '이름'을 붙이는 이유는, Regex 형태와 일치하지 않을 때 다음과 같이 stderr로 출력되기 때문입니다. 예를 들면 'Token parameter [name=str] equals to "abc", doesn't correspond to pattern "[ab]+"'의 형식으로 말이죠. 이 경우 입력받은 문자열은 abc, ptrn에는 [ab]+, variableName에는 str이 들어가 있는 것입니다. 이렇게 하면 어떤 변수가 조건을 부합하지 않는지 알 수 있습니다. readWord도 똑같이 오버로딩되어 있습니다.

  • long long readLong():
    long long 타입의 정수를 하나 읽습니다. 작동 원리는 readWord()를 부른 다음에 읽은 string이 long long 타입의 정수인지 판별하고(판별 과정은 생략하겠습니다), 그 값을 long long 으로 return하는 형식입니다. 이하 다른 readxxx함수도 마찬가지 방식으로 작동합니다.

  • int readInteger(), int readInt():
    int 타입의 정수를 하나 읽습니다.

  • double readReal(), double readDouble():
    double 타입의 실수를 하나 읽습니다.

  • long long readLong(long long minv, long long maxv, const std::string& variableName = ""): 구조적으로는 readLong()과 동일하지만, 이 경우 읽은 값이 minv 이상 maxv 이하여야 합니다. 그렇지 않을 경우 variableName에 들어가는 변수 명을 이용하여 'Integer parameter [name=n] equals to 132039, violates the range [1,100000]' 등으로 에러 메시지를 냅니다. 사실 이쪽을 사용하는 것이 권장됩니다. 

  • int readInt(int minv, int maxv, const std::string& variableName = ""):
    구조적으로는 readInt()와 동일하지만, 이 경우 읽은 값이 minv 이상 maxv 이하여야 합니다. variableName은 에러 메시지에 출력할 변수명입니다. readInteger 함수도 해당 버전이 있습니다.

  • double readDouble(double minv, double maxv, const std::string& variableName = ""):
    구조적으로는 readDouble()와 동일하지만, 이 경우 읽은 값이 minv 이상 maxv 이하여야 합니다. variableName은 에러 메시지에 출력할 변수명입니다. readReal 함수도 해당 버전이 있습니다.

  • double readStrictDouble(double minv, double maxv, int minAfterPointDigitCount, int maxAfterPointDigitCount, const std::string& variableName = ""):
    구조적으로는 readDouble(minv, max, variableName)과 동일하지만, 소수점의 자릿수가 minAfterPointDigitCount 이상 maxAfterPointDigitCount 이하이어야 합니다. readStrictReal 함수도 해당 버전이 있습니다.

  • std::string readString(), std::string readLine(): EOF나 EOLN(End of Line, 즉 줄바꿈을 의미하는 글자입니다)을 만날 때까지 입력된 값을 읽어나가, 그 읽은 값은 std::string의 형태로 반환합니다.

  • std::string readString(const std::string& ptrn, const std::string& variableName = ""):
    readString()과 동일하나, 읽은 값이 ptrn로 주어진 Regex 패턴을 만족해야 하며, 아닐 경우 variableName에 주어진 변수명을 토대로 에러를 띄웁니다.

  • void readEoln():
    줄바꿈 문자('\n')를 읽습니다. 줄바꿈 문자가 아닐 경우, 'Expected EOLN'이라면서 에러를 띄웁니다. 이 함수는 Windows에서는 0x0D와 0x0A를 읽으며(CR LF), OS X(버전 10 이상)와 *nix에서는 0x0A를 읽습니다(LF).

  • void readEof():
    파일의 끝을 읽습니다. 파일의 끝이 아닐 경우, 'Expected EOF'라며 에러를 띄웁니다.

  • inline void ensuref(bool cond, const char* format, ...):
    cond에 입력된 조건이 만족되지 않을 경우, format을 stderr로 출력합니다. printf처럼 가변형 인자를 가진 함수입니다. 이 함수는 주로 변수를 입력받은 다음에, 변수의 범위로만은 따질 수 없는 성질을 확인하고자 할 때 사용합니다. 

7. Tests (1 - Generator with testlib.h)에서 선보였던 generator가 기억나십니까?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "testlib.h"
#include <iostream>
 
int main(int argc, char* argv[])
{
    registerGen(argc, argv, 1);
    
    int query=atoi(argv[1]);
    int max_int=atoi(argv[2]);
    
    cout << query << endl;
    
    for(int i=0;i<query;i++)
    {
        int n=rnd.next(0, max_int);
        int r=rnd.next(0, n);
        std::cout << n << " " << r << endl;    
    }
    return 0;
}
 
cs


query와 max_int의 최댓값이 80000이라고 문제에 명시가 되어 있다면, 이 generator에 해당하는 validator의 한 예시는 다음과 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "testlib.h"
 
int main()
{
    registerValidation();
 
    int q = inf.readInt(180000"q");
    inf.readEoln();
 
    for (int i = 0; i < q; i++)
    {
        int n = inf.readInt(080000"n");
        inf.readSpace();
        int r = inf.readInt(080000"r");
        ensuref(r <= n, "'r' must not  exceed 'r'");
        inf.readEoln();
        
    }
    inf.readEof();
    return 0;
}
 
cs


q를 읽은 다음에 줄바꿈 문자을 읽고, 'n을 읽는다 ->  공백을 읽는다 -> r을 읽는다 -> r <= n인지 확인한다 -> 줄바꿈 문자를 읽는다'를 q번 반복합니다. 그 다음에는 EOF을 읽으며 끝납니다. Polygon의 Well-formed policy는 이런 whitespace을 각 문제별로 규격화하기 우해서 있는 것이지요. space가 난잡하게 어떤 곳은 1개, 어떤 곳은 3개 이렇게 있으면 validator가 작동하기 힘들기 때문입니다.


하나 더 예시를 들어보겠습니다. 이 코드는 다음 문제의 validator로 작동합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "testlib.h"
char status[1000001];
int main()
{
    registerValidation();
 
    int n=inf.readInt(1100"n");
    inf.readEoln();
 
    for(int i=0;i<n;i++)
    {
        char ch=inf.readChar();
        ensuref(ch=='+'||ch=='-'"Invalid operation %c, + or - expected", ch);
        inf.readSpace();
        int x=inf.readInt(11000000"x");
        if(status[x]==ch)
        {
            ensuref(ch!='+'"Invalid operation %c %d, %d is already in the library", ch, x, x);
            ensuref(ch!='-'"Invalid operation %c %d, %d is not in the library", ch, x, x);
        }
        status[x]=ch;
        inf.readEoln();
    }
    inf.readEof();
    return 0;
}
 
cs


입력 데이터가 복잡해질수록 validator도 복잡해지지만, validator는 모든 invalid input을 잡아야 하기에, 신중히 코딩할 필요가 있습니다. 그래서 Polygon은 validator를 시험해볼 기회를 줍니다. Set validator 밑을 보면 다음과 같습니다.



Test를 하나 만들고 설명하겠습니다. Add test를 누르면 다음과 같은 화면이 나타납니다.



각 항목을 설명하자면 다음과 같습니다.Validator test #: test의 번호입니다. 여기서도 마찬가지로, test의 개수는 1000개를 넘을 수 없습니다(1~1000).

  • Multiple tests: 저 체크박스가 활성화되어 있으면 ===을 경계로 여러 개의 test를 한꺼번에 만들 수 있습니다. 특히 Validator test는 Archive로 한꺼번에 올릴 수도 없고, Create를 누르면 Tests의 경우처럼 다시 이 창으로 이동하는게 아니라 Validator 탭으로 다시 이동하기 때문에, 여러 개를 한꺼번에 만드는 것을 추천합니다.

  • Input(s): test data를 여기에 적으면 됩니다. 공백이나 줄바꿈에 유의하면서 입력하시기 바랍니다. 참고로 Multiple Test를 만들 때 구분을 ===로 한다고 했는데, 사용 예시는 다음과 같습니다.

    1

    2 1

    ===

    0


      ===를 사용할 때는, ===를 치고 바로 줄바꿈을 해야 합니다. 즉 === 직전까지(2 1 이후의 줄바꿈까지)가 1번째 test이고, === 다음 줄부터 2번째 test로 인식되는 것입니다.

    • Verdict(s): test가 올바른지 아닌지를 적는 칸입니다. 올바르면 1 또는 VALID를, 올바르지 않으면 0 또는 INVALID를 입력하면 됩니다. 영단어로 표기할 경우 대소문자 구별은 하지 않으며, test가 여러 개일 때는 각 줄마다 올바른지 아닌지를 해당 양식으로 적으시면 됩니다(즉, 공백으로 구분지을 수 없습니다).
    이런 식으로 만든 다음에 Create를 누르면 Validator 탭에 해당 test가 추가된 것을 볼 수 있습니다. 비록 지금은 Main Solution이 없어서 이를 지금 당장 확인할 수는 없지만, 다 만든 문제(위에 있었던 generator가 쓰이는 문제)에서는 다음과 같이 나옵니다.


    31번과 32번 test를 보면, INVALID한 이유가 상세히 나옵니다. 다만 예상했던 결과와 다르게 나오면 그 열의 배경색깔이 빨간색이 됩니다. 다음과 같이 말입니다.


    43번부터 45번까지는 VALID라 설정했지만 validator가 INVALID라 판별하였기에 빨간색이 되었습니다. 반대로 INVALID라 설정한 것이 VALID일 경우도 저렇게 됩니다. 그런 경우가 있다면, validator를 고쳐야겠지요!

    validator를 만들면서 주의해야 할 점은 특히 input의 범위나 양식은 올바른데 실제로는 올바르지 않은 경우(중복되었다든지, 추가적인 제약조건이 있다든지)도 고려해야 한다는 점입니다. 참고로 testlib.h 폴더 있는 validator 중에서도 bipartite graph validator 등이 있으니 참고하셔도 좋을 듯 합니다.

    그 외에 testlib.h zip 파일에 있는 'validate-using-testset-and-group.cpp' 파일을 보면 testset과 group에 따라 validator가 다르게 작동하는 것의 예시도 나와 있습니다. 이를 사용하면 group 또는 testset에 따른 sub-task와 그것의 제약조건을 고려한 validator를 만들 수 있습니다. sub-task에 주어진 제약조건을 어기는 test가 있으면 곤란하기 때문입니다(ex. N<=15, K<=100이라고 했는데 N=1, K=1000인 경우가 있을 때). 

    PS. 43번 test가 INVALID한 이유가 '0이 [1,80000] 범위에 있지 않아서'가 아니라 '정수가 아니어서'인 이유는, 복붙 과정에서 BOM이라는 문자가 끼어들어갔기 때문입니다. 하지만 직접 타이핑하면서 생길 문자는 아니므로, 특이한 케이스라고 보시면 되겠습니다.


    'Polygon' 카테고리의 다른 글

    11. Checker  (0) 2015.08.17
    10. Solution Files  (0) 2015.08.12
    8. Tests (2 - Generator with Freemarker)  (0) 2015.08.09
    7. Tests (1 - Generator with testlib.h)  (0) 2015.08.02
    6. testlib.h  (0) 2015.07.29
    Posted by Evenharder
    ,

    일러두기 : 제가 정작 generator를 만드는 것 자체를 간단히 넘어간 면이 없지 않아 있지만 우선 이는 문제에 따라서 그 양상이 너무나도 다르고(input이 자연수 한 개뿐인 문제도 있는 반면, 그래프 상태를 입력을 받고 추가적으로 쿼리까지 받는 복잡한 문제들도 존재하기 때문입니다), 거기에 여러 커팅등을 방지하는 corner case를 만드는 것까지 고려하면 이건 문제를 만드는 사람이 해결해나가야 하는 일입니다. Codeforces Round에서도 '이거 정해는 O(n^2*m)인데 왜 O(n^3)이 통과되나요' 등의 일이 자주 발생하기 때문입니다. 그래서 problem setter가 여러 분들의 도움을 받는다는 것을 알 수 있습니다. 이와 별개로, 가끔씩은 test case를 만드는 것 자체가 하나의 문제가 되는 경우도 많습니다(ex. 점 n개가 주어질 때 세 점을 지나는 직선이 존재하는지의 여부). 사실 이쪽이 문제를 만드는 데 많은 시간을 투자하게 되는 부분이기도 합니다.


    아무튼 testlib.h로 test case를 만드는 Generator를 작성하셨다면(참고로 testlib.h에는 bipartite graph generator라든지, tree generator라든지 만들기 힘든 generator도 있습니다), Polygon에 test를 zip 파일로 올리는 수도 가능하겠지만, 가장 좋은 방법은 만든 generator를 Polygon에서도 작동하게 만드는 것 아닐까요?


    5. Files에서 이미 나왔던 내용이지만 이런 종류의 file은 Files - Source Files에 업로드를 할 수 있습니다. 업로드를 합시다.


    업로드를 한 후에 Tests로 이동하여 Script로 이동해 봅시다. Script의 모습은 다음과 같습니다.


    이 창은 특수한 구문을 이용하여 자동으로 test를 추가해 줍니다. Generator의 이름이 randomGen(확장자는 무시하고 씁니다)이라고 하고, 기본적으로 인자를 2개 받는다고 합시다. 그러면 generator를 부를 때는 'randomGen 인자1 인자2' 형태로 쓰시되, '인자1'과 '인자2'에는 실제 프로그램에 전달할 인수의 값을 집어넣으시면 됩니다. randomGen 10 10 처럼 말입니다.


    하지만 이것만 적으면 안 됩니다. 이렇게 만들 test가 몇 번째 test인지 알려주어야 합니다. 알리는 방식은 크게 3가지가 있습니다.

    • randomGen 10 10 > 1 : 말 그대로 1번째 test를 randomGen 10 10을 실행했을 때 만들어지는 값으로 설정한다는 뜻이며, 이 경우 표준 입출력을 사용합니다. 뒤에 붙이는 숫자는 이전에 언급했듯이 1에서 1000까지만 있을 수 있으며(test 최대 개수가 1000개입니다)다만 Manual하게 만든 해당 번호의 test가 이미 있을 경우에는 사용할 수 없습니다. 또한 Script에 같은 test 번호를 가리키는 구문이 있을 수 없습니다.

    • randomGen 10 10 > {1} : 이 경우, 1번째 test를 randomGen 10 10을 실행했을 때 나오는 test로 설정한다는 뜻이나, 파일 입출력의 형식으로 지정됩니다일반적인 숫자 사용법과는 다르게, 범위를 지정할 수 있습니다. {1-3,7}의 경우 1,2,3,7에 randomGen 10 10이 들어가게 됩니다.
      다만 저 경우 각 test가 모두 같게 되는지, 아니면 Polygon에서 자체적으로 이 뒤에 parameter를 더 추가해 seed를 바꾸어 다른 test가 되는지는 잘 
      모르겠습니다.

    • randomGen 10 10 > $ : 이 경우, 해당하는 test는 Script에서 저렇게 test 번호가 명시적으로 나타난 경우를 모두 처리한 다음, 가능한 제일 작은 번호 값을 가지게 됩니다. $는 파일 입출력을 의미하는 {} 안에 넣어 사용할 수 없습니다.
    실제로 파일 입출력과 표준 입출력을 섞어서 쓰는 경우는 거의 없고, 또 testlib.h에서도 표준 입출력을 사용하도록 장려하고 있기 때문에 {}는 무시하면 됩니다만......{}처럼 범위 지정을 못한다는 것이 문제입니다.

    일단 그 문제는 잠시 제쳐두고, 그렇게 생성된 test를 한 번 봅시다. 다음과 같은 script를 시험삼아 한 번 돌려보았습니다.

    randomGen 10 10 1 > 1

    randomGen 10 10 2 > 3

    randomGen 10 10 3 > {4}

    randomGen 10 10 4 > $

    randomGen 10 10 5 > $


    parmeter는 2개만 전달하는 것 아니었나요라고 질문할 수 있습니다. 추가적으로 3번째 인자를 전달하는 것의 의미는 다음과 같습니다.

    • 저런 식으로 표시하지 않으면 test가 중복(coincide)된다고 에러를 띄웁니다.

    • 또, 저렇게 추가적인 인자를 보내면 generator 내의 함수 registerGen에서 seed값을 다르게 가지게 되어, 실제 test data도 다르게 됩니다.
    아무튼 저렇게 script를 만들고, Save Script를 누르면 script에 따른 test case가 생깁니다. 한번 볼까요?


    Manual하게 만든 test와는 달리, Content에 test data가 아닌 script가 들어가 있습니다. 단, 중괄호는 사라져 있습니다. 또, $를 사용한 test들의 위치가 번호에 맞게 들어간 것을 알 수 있습니다(참고로 이 때 밑에 적었던 script 자체의 순서도 이에 따라 변경됩니다). 이 때 Preview Tests를 누르면 실제 test data를 볼 수 있습니다. 물론, 아직은 validator, checker, main solution 등이 없어서 무용지물입니다.


    이 때 edit을 누르면, 7. Tests (1 - Generator with testlib.h)에서 생략하고 넘어간 Script Type의 test를 볼 수 있게 됩니다.



    주목할 점은 Script Line과 옆에 있는 체크박스입니다. Script Line에는 Script 창에 써넣은 구문이 각 test에 대해서 들어가 있습니다. 이를 수정하면 전체적인 script 역시 수정되며, 옆에 있는 체크박스 (Check it if generator produces test as file (doesn't use stdout)) 역시 수정시 그대로 script가 수정됩니다. 해당 체크박스는 파일 입출력을 사용 유무를 나타냅니다.


    그럼 다시 그 문제로 돌아와서......표준 입출력을 사용할 때 저렇게 대량의 test를 어떻게 양산해낼까요? 물론 모든 script를 출력하는 프로그램을 만들어서 (이 정도 코딩은 정말 간단하니까요) Ctrl CV하는 수도 있습니다. 하지만 그것보다 훨씬 더 효율적인 방법이 존재합니다. 바로 Freemarker Template을 이용하는 것입니다.


    저도 Freemarker Template은 잘 모르지만, Polygon Tests에서 Script를 만들 때 필요한 지식은 얼마 되지 않습니다. 다음은 그 내용입니다.

    • 변수 선언 및 간단한 연산은 <#assign i = 2/>, <#assign i = i + j /> 등의 식으로 #assign을 이용하여 할 수 있습니다.

    • for문의 경우, <#list 1..5 as i> ... </#list> 등의 식으로 할 수 있는데, i라는 변수를 1에서 5까지 1씩 증가시키면서 <#list 1..5 as i> 와 </#list> 사이에 있는 구문을 반복해서 실행한다는 의미입니다. #list는 중첩이 가능합니다.

    • 변수를 사용할 때는 ${변수명}으로 사용합니다. i를 사용하고 싶으면 ${i}, num을 사용하고 싶으면 ${num}......

    • Freemarker Template을 사용할 경우 generator 구문 맨 끝에 번호를 나타내는 인자에는 무조건 $가 와야 합니다. 숫자나 중괄호는 올 수 없습니다.

    • Freemarker Template으로는 random을 지원하지 않습니다. 대부분의 경우 Freemarker Template으로 random을 돌릴 이유가 없기도 하지만 말이죠.

    • Freemarker Template은 SPACE나 TAB으로 들여쓰는 것이 의무가 아닙니다.
    그러면 다음과 같은 간단한 구문으로 test를 만들 수 있습니다.

    <#assign i = 1/>
    <#list 1..100 as i>
        randomGen 50000 50000 ${i} > $
    </#list>


    여러 개의 generator를 이용해서 이렇게 만드는 수도 있습니다.


    <#assign num = 1/>

    <#assign i = 1/>

    <#list 1..100 as i>

        <#assign j = 1/>

        <#list 1..7 as j>

            randomGen ${i*i*8} ${i*i*8} ${num} > $

            <#assign num = num + 1/>

        </#list>

        CenterGen ${i*i*8} ${i*i*8} > $

        <#assign num = num + 1/>

        xStackGen ${i*i*8} ${i*i*8} > $

        <#assign num = num + 1/>

        yStackGen ${i*i*8} ${i*i*8} > $

        <#assign num = num + 1/>

    </#list>


    이런 방식으로, test를 만들 수 있습니다. 하지만 test를 만드는 것이 문제를 만드는 것에서 가장 중요한 부분 중 하나라는 것을 명심하시길 바랍니다. 일례로 바로 위의 코드도 CenterGen 등의 다른 generator를 사용한 이유 역시 커팅 등을 사용한 풀이를 TLE 내기 위해서이니까요 (참고로 CenterGen 등의 generator는 random generator가 아님을 밝힙니다). 물론 실제 test set에 test를 저렇게 1000개를 넣고 돌리지는 않을 겁니다. 저건 어디까지나 시험용일 뿐입니다. 실제 test set은 많아야 100개 남짓 되는 test가 들어가고, 이를 어떻게 구성할 지는 여러분의 몫이기 때문입니다.

    'Polygon' 카테고리의 다른 글

    10. Solution Files  (0) 2015.08.12
    9. Validator  (1) 2015.08.11
    7. Tests (1 - Generator with testlib.h)  (0) 2015.08.02
    6. testlib.h  (0) 2015.07.29
    5. Files  (0) 2015.07.28
    Posted by Evenharder
    ,