6. testlib.h에서 이미 적었듯이 Tests에서 test, 즉 문제의 input들을 만드는 것을 우선 포스팅하겠습니다. Tests 탭에 들어가면 다음과 같은 화면이 나타납니다.
순차적으로 위에서부터 설명하겠습니다.
Tests(0): 이는 Tests라는 test set에 문제가 0개 있다는 것을 의미하여, Tests라는 test set에 이동하라는 하이퍼링크입니다. Tests는 기본적으로 생기는 test set입니다.
Delete Current: 현재 자신이 있는 Test set를 지우는 링크입니다. 이를 만족하려면 다음과 같은 조건을 만족해야 합니다.
- Test set이 Tests가 아닐 것이며
- test가 하나도 있지 않아야 합니다. 즉 모든 test를 삭제한 상태여야 합니다.
- Create Current: 새로운 Test set를 만드는 링크이며, 누르면 새로운 이름을 입력하라고 나옵니다. 이 때 이름의 조건은 다음과 같습니다.
- 이미 존재하는 Test set과 이름이 중복되면 안 되며
- 알파벳 소문자/대문자, 숫자 그리고 underscore(_ : 0x5F)만 사용할 수 있습니다.
- Testset: 이 test set의 이름을 의미합니다. test set은 한 번 만들어지면 이름을 자체적으로는 바꿀 수 없지만, 후술할 복사 기능을 이용해서 사실상의 이름 변경을 할 수 있습니다.
- Test count: 이 test set에 있는 test의 개수를 의미합니다. 한 test set에 test는 최대 1000개 들어갈 수 있습니다.
- Enable groups: test들의 그룹을 활성화하는 버튼입니다. 그룹을 지으면 한 test set에서도 이들을 묶을 수 있습니다. 이러면 나중에 Invocation에서나, Validator에서나 그룹별로 따로 작업을 수행할 수 있습니다. 이는 그룹이 활성화되어 있을 때만 적용됩니다. 다만 그룹을 만든 다음에 비활성화해도 그룹이 사라지지는 않습니다.
- Preview Tests: Test set을 한 번 실제로 프로그램으로 돌려보아, 그 결과를 미리 봅니다. 이는 Validator, Solution, Checker 등의 모든 것들이 기본적으로 필요하기에, 지금 당장은 큰 의미가 없는 링크입니다.
- Add Tests: test를 추가할 수 있는 링크입니다. 한 번 눌러봅시다.
- Testset: 이 test set의 이름을 의미합니다.
- Test #: 이 test가 해당 test set에서 몇 번째 test인지를 의미합니다. 같은 번호를 가지는 test가 2개 이상 존재할 수 없기 때문에, 일반적으로는 가능한 test 번호 중 최저의 숫자를 적어놓지만, 번호를 임의로 바꿀 수는 있습니다. 예를 들면 Test #1 대신 Test #23이라든지 말이죠. 그러나 일반적으로는 순차적으로 Test를 만들게 됩니다. 상식적으로 Test가 1부터 시작하지 23부터 시작할까요. 이렇게 숫자들의 순서 및 배열이 이상한 경우 어떻게 되는지는 후술하겠습니다.
- Type: test를 어떤 방식으로 추가할 것인지를 의미하는 탭이며, 두 가지 선택지가 있습니다. 'Manual'과 'Script'입니다. Manual은 직접 더한다는 뜻이고, Script는 별도의 스크립드를 이용하여 test를 더한다는 뜻인데, 우선 이 글에서는 Manual만 다루고, Script는 다음 포스팅에서 Freemarker를 다룰 때 같이 설명하겠습니다.
- Data: test 그 자체입니다. 입력해야 할 대상을 입력하시기 바랍니다. 다만 여기에 test를 입력할 때 General Info에서 'Are tests well-formed?'를 활성화시켰다면 3. General Info의 'Well-formed'에서 설명한 규칙이 적용되어야 합니다. 적용되지 않을 경우 강제로 규칙들을 적용하며, ‘Added test has been reformatted because of the problem well-formed policy’ 라는 구문이 create를 누르고 다음 창에 나타납니다. test의 규모가 클 때 어떻게 해야 하는지는 조금 있다 다루겠습니다.
- Use in statements: Statement에서 만들던 pdf 파일을 기억하십니까? Statement에서는 예제를 더하는 기능이 없었습니다. 이를 여기서 시행할 수 있습니다. 누르면 다음과 같이 나타납니다.
옆에 또 하이퍼링크가 있는 것을 알 수 있습니다. 누르면 다음과 같은 입력창이 추가적으로 나타납니다.
추가적으로 나타나는 항목들은 다음과 같습니다.
- Input in statements: 입력하는 값이 너무 많아서 pdf로 옮기기에는 좀 그런 상황일 때는 statement에 사용하지 않는 것이 정상입니다. 하지만 꼭 사용해야 할 경우 저렇게 statement에 어떻게 이 test를 나타낼지를 입력하는 창이 주어집니다. 예를 들면 처음 6줄만 쓰고 말줄임표로 생략한다든지......만일 모든 input data를 사용하고 싶다면 빈칸으로 내버려두시면 됩니다.
- Output in statements: 생략당한 입력이 있으면 그 입력에 대한 출력도 생략당하겠죠. 마찬가지입니다. 기본적으로 Input in statements가 비어있지 않으면 이 입력창 역시 비어있으면 안 됩니다(역시, 비어있으면 모든 output data를 출력하겠다는 뜻입니다). 다만 input 쪽을 다 출력했는데 output은 일부 생략한다의 방식은 가능합니다.
- Verify output for statements: 이 기능은 실제 statement에 output이 제대로 문제 형식에 맞추어서 작동하는지를 판별해야 하는데, 뭐 input을 막 조작하든 output을 조작하든 잡아내질 못합니다......결론은 무쓸모.
- valid한 test여야 합니다. 즉, Validator와 Checker를 거쳐 나올 수 있는 test여야 합니다.
- input이 5000바이트를 넘어갈 수 없습니다. output 쪽은 잘 모르겠습니다.
- 아주 중요한 불문율로, Statement에 나오는 test들은 test set에서 가장 앞에 있어야 합니다.
- Description: test에 대해 기타 사항을 적는 곳입니다.
- void registerGen(int argc, char* argv[], int randomGeneratorVersion):
이 프로그램이 generator라고 선언하는 코드로, main 함수 첫 줄에 쓰는 것이 권장됩니다. 이 함수는 next 함수(randomizer 함수)의 seed값을 정해, next 함수가 정상적으로 사용될 수 있게 작용합니다. 다시 한 번 쓰지만, rand()나 srand()는 쓸 수 없습니다. 내부적으로 사용하면 바로 stderr로 해당사항을 출력시킵니다. 이런 방법을 쓰는 이유는 아무래도 rand() 등의 함수들이 컴파일러나 IDE에 따라 그 값이 다르기 때문이라고 생각됩니다.- int argc : 네. main 함수의 그 argc 맞습니다. 실제로 main 함수의 그 argc를 넘겨야 합니다.
- char* argv[] : 네. 역시 그 argv[] 맞습니다. 마찬가지로 main 함수의 그것을 넘겨야 합니다.
- int randomGeneratorVersion : 1로 고정입니다. 초창기 testlib.h는 0을 사용했지만 이제는 버전이 높아져서 새로운 값인 1로 사용합니다. 0과 1 이외의 값을 넘길 경우 stderr로 해당사항을 알립니다.
argc와 argv는 내부적으로 바로 seed값을 결정하는데 사용됩니다. argc와 argv가 가지는 자체적인 의미와, argc와 argv의 사용법은 후술하겠습니다.
- double next():
parameter가 없는 유일한 next입니다. 0 이상 1 미만의 double형 실수를 return합니다. - T next(T n):
0 이상 n 미만의 T형 수를 return합니다. n이 음수거나 값이 너무 크거나 하면 stderr로 해당 사항을 알립니다. - T next(T from, T to):
from 이상 to 이하의 T형 수를 return합니다. to가 from보다 작거나, to나 from의 값이 너무 클 경우 stderr로 해당사항을 알립니다. - std::string next(std::string ptm):
ptm은 Regex에 따른 std::string(내지는 char*)입니다. Regex는 string에서 해당하는 패턴을 찾고자 할 때 사용하는 입력법인데요, ptm을 그런 식으로 입력해야 합니다. 물론 testlib.h가 모든 Regex 스펙을 따르고 있지는 않습니다. 간단한 것만 구현해서 사용하고 있는데요, 어차피 랜덤한 것을 만드는데는 별 상관 없습니다. Regex에서 사용할 수 있는 구문들은 다음과 같습니다.- 문자들의 집합은 대괄호 안에 넣어서 사용합니다. 예를 들어 [ac]는 a와 c를, [a-z]는 a부터 z까지, [^a-z]는 a-z를 제외한 모든 문자를, [a-z][A-Z]는 알파벳 대소문자 모두를, [0-9]는 숫자 0부터 9까지를 의미합니다.
- 문자들의 개수는 대괄호 뒤에 중괄호를 붙여서 사용합니다. 예를 들어 [a-c]{5}는 a부터 c까지의 문자, 즉 a,b,c 셋 중 하나를 무작위로 골라 길이 5의 문자열을 만들고, [a-z]{1,100}의 경우 알파벳 소문자로만 이루어진 1자리부터 100자리의 문자열을 만들어냅니다.
이 중괄호는 바로 앞의 집합에만 적용됩니다. [a-z][A-Z]{10}의 경우 알파벳 소문자를 무작위하게 1개, 알파벳 대문자를 무작위하게 10개를 뽑습니다.
또, %d 등을 통해서 범위를 변수로 조정할 수도 있습니다.
- black|white 문자열의 경우 black 또는 white입니다.
- 문자를 선택적으로 넣고 싶을 때는 ?를 사용합니다. 그 예시로 -?[1-9]가 있습니다. 이 경우 앞의 -가 선택적으로 들어갑니다. 그 결과 -9부터 9까지의 0이 아닌 자연수를 랜덤하게 만들게 됩니다.
- 문자를 반복적으로 선택하고 싶을 경우 * 또는 +를 사용할 수 있습니다. [0-9]*의 경우, 0부터 9까지의 수를 0개 이상으로 연속적으로 선택하며, [0-9]+의 경우 0부터 9까지의 수를 1개 이상으로 연속적으로 선택합니다.
- 추가적으로 실험을 해보고 싶으시면 이 링크에서 한 번 해보시길 바랍니다. 다만 testlib.h에서 지원하지 않는 기능들도 지원하니 유의하세요.
- 문자들의 집합은 대괄호 안에 넣어서 사용합니다. 예를 들어 [ac]는 a와 c를, [a-z]는 a부터 z까지, [^a-z]는 a-z를 제외한 모든 문자를, [a-z][A-Z]는 알파벳 대소문자 모두를, [0-9]는 숫자 0부터 9까지를 의미합니다.
- T wnext(T n, int type):
w는 weight을 의미합니다. 가중치가 어떻게 구현되는지는 다음과 같습니다. - type이 양수일 경우 |type+1|개 만큼의 0 이상 n 미만의 T형 수를 뽑은 다음, 그 중 최댓값을 return합니다. 과정은 for문으로 이루어집니다.
- type이 음수일 경우 |-type+1|개 만큼의 0 이상 n 미만의 T형 수를 뽑은 다음, 그 중 최솟값을 return합니다. 과정은 for문으로 이루어집니다.
- type이 0일 경우 T형에 해당하는 next 함수와 동일한 역할을 합니다.
- void shuffle(_RandomAccessIter __first, _RandomAccessIter __last):
random access가 가능한 iterator를 돌려가면서 first와 last 사이의 원소들을 무작위하게 섞습니다. 주의할 점은 algorithm 헤더의 std::random_shuffle을 사용하면 안 된다는 점입니다.
- typename Container::value_type any(const Container& c):
container c에 있는 원소 중 하나를 랜덤하게 선택하여 리턴합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include "testlib.h" #include <iostream> using namespace std; int main(int argc, char* argv[]) { registerGen(argc, argv, 1); cout << rnd.next(1, 1000000) << endl; return 0; } | cs |
1부터 100000 중 하나의 값을 무작위하게 출력하는 Generator입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include "testlib.h" #include <iostream> using namespace std; int main(int argc, char* argv[])cc { registerGen(argc, argv, 1); cout << rnd.next("[a-zA-Z0-9]{1,1000}") << endl; return 0; } | cs |
알파벳 소문자, 대문자, 숫자를 포함하는 길이 1에서 1000까지의 문자열을 출력하는 프로그램입니다.
하지만 이러면 또 의문이 생깁니다. '저 프로그램들의 길이 범위는 정해져 있는 것이 아닌가? 우리가 조절할 수 있게 할 수는 없는건가?' 결론은 '조절할 수 있다'입니다. 이를 하기 위해서는 main 함수의 인자인 argc와 argv의 의미를 파악해야 합니다.
흔히 int main()으로 생략되기도 하는 main 함수. main 함수의 형태는 비표준 확장을 제외하면 다음 2가지 중 하나를 지녀야 한다고 명시되어 있습니다.
int main()
int main(int argc, char* argv[])
이 중 argc와 argv의 의미는 다음과 같습니다.
int argc: argv에 전달되는 인자의 개수입니다. argc는 0 이상의 값입니다.
char* argv[]: 실행 환경 자체에서 주어진 인자들입니다. argc가 1 이상의 값일 경우 argv[0]은 프로그램 이름을 나타내는 char*이며, argv[1] 부터 argv[argc-1]까지는 프로그램에 전달되는 인자(parameter)입니다. argv[argc]는 null pointer를 가리킵니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #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를 프로그램 자체의 인자로 받아, query를 첫째 줄에 출력한 다음, max_int 개 만큼의 0 <= r <= n을 만족하는 n, r을 각 줄에 공백으로 구분지어 출력하는 generator입니다.
이런 식으로, 우리는 프로그램이 원하는 범위만큼, 원하는 개수만큼 test를 출력하게 만들 수 있습니다. 프로그램의 인자 자체를 집어넣으려면 Windows의 경우, cmd 창 등의 shell을 이용해서 프로그램 이름 옆에 공백으로 인자를 입력하면 됩니다.
주제가 약간 옆으로 샜지만, 밑에 보면 Script라고 칸이 있으며 그 옆에 약간의 설명이 나와 있습니다. 이에 관해서는 다음 글에서 포스팅하겠습니다.
-UPD 2015.08.08 원래는 testlib.h의 자세한 함수 기능 등을 서술하려 했는데, 굳이 그럴 필요가 없어 보이고, 또 testlib.h 내에 자체적으로 주석 등으로 설명이 잘 되어 있기 때문에 서술을 생략하겠습니다.
-UPD2 argc와 argv 설명을 추가했습니다. 원래 다음 포스팅에 실으려 했슨데 생각해보니 여기에 넣지 않으면 Generator를 만들 수가 없게 되어서......
-UPD3 Group 관련 설명을 추가했습니다.
'Polygon' 카테고리의 다른 글
9. Validator (1) | 2015.08.11 |
---|---|
8. Tests (2 - Generator with Freemarker) (0) | 2015.08.09 |
6. testlib.h (0) | 2015.07.29 |
5. Files (0) | 2015.07.28 |
4. Statement (0) | 2015.07.28 |