Skip to content

Commit

Permalink
Support ICPC-style problem (multiple test cases per file)
Browse files Browse the repository at this point in the history
Resolve ia-toki#43
  • Loading branch information
fushar committed Aug 4, 2015
1 parent c08068d commit 0a6cdd0
Show file tree
Hide file tree
Showing 12 changed files with 402 additions and 29 deletions.
51 changes: 51 additions & 0 deletions docs/advanced_concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,54 @@ Complex predicates in constraints
---------------------------------

Similarly, methods used in defining constraints (**Constraints()**, **SubtaskX()**) must be free of custom loops and conditional branches. If you want to use complex predicate, such as determining whether the input is a tree, create a custom private helper method that return boolean (whether the input is a tree).

Multiple test cases per file (ICPC-style)
------------------------------------------------

tcframe supports ICPC-style problem as well. In this style, for each of the **SampleTestCases()**, **TestCases()**, and **TestGroupX()** methods, the test cases will be combined into a single file. The file is prepended with a single line containing the number of test cases in it.

To write, an ICPC-style test cases generator, first write the runner as usual, assuming that the problem not ICPC-style. Then, apply the following changes.

Problem specification class
***************************

- Declare an integer variable (e.g., **T**) that will hold the number of test cases in a single file.

.. sourcecode:: cpp

protected:
int T;

...

- In the problem configuration, call **setMultipleTestCasesCount()** method with the previous variable as the argument.

.. sourcecode:: cpp

void Config() {
...

setMultipleTestCasesCount(T);

...
}

- The input format specification should not contain **T**. It should specify the format of a single test case only. The number of test cases will be automatically prepended in the final combined test case input file.

- We can impose a constraint on **T**, inside **MultipleTestCasesConstraints()** method.

.. sourcecode:: cpp

void MultipleTestCasesConstraints() {
CONS(1 <= T <= 20);
}

Generation specification class
******************************

No changes are necessary.

Solution program
****************

Although the input format only specifies a single test case, the solution should read the number of test cases in the first line. In other words, the solution will read the final combined test cases input file.
22 changes: 18 additions & 4 deletions include/tcframe/constraint.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,21 +58,35 @@ class ConstraintsCollector {
subtasks.push_back(new Subtask(curSubtaskId));
}

void setMultipleTestCasesMode() {
this->multipleTestCasesMode = true;
}

void addConstraint(function<bool()> predicate, string description) {
if (subtasks.empty()) {
subtasks.push_back(new Subtask(-1));
if (multipleTestCasesMode) {
multipleTestCasesConstraints.push_back(new Constraint(predicate, description));
} else {
if (subtasks.empty()) {
subtasks.push_back(new Subtask(-1));
}

subtasks.back()->addConstraint(new Constraint(predicate, description));
}

subtasks.back()->addConstraint(new Constraint(predicate, description));
}

vector<Subtask*> collectSubtasks() {
return subtasks;
}

vector<Constraint*> collectMultipleTestCasesConstraints() {
return multipleTestCasesConstraints;
}

private:
bool multipleTestCasesMode = false;
int curSubtaskId = 0;
vector<Subtask*> subtasks;
vector<Constraint*> multipleTestCasesConstraints;
};

}
Expand Down
6 changes: 6 additions & 0 deletions include/tcframe/exception.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ class PrintingException : public TestCaseException {
: TestCaseException(message) { }
};

class MultipleTestCasesConstraintsSatisfiabilityException : public TestCaseException {
public:
MultipleTestCasesConstraintsSatisfiabilityException(vector<Failure> failures)
: TestCaseException(failures) { }
};

class SubtaskSatisfiabilityException : public TestCaseException {
public:
SubtaskSatisfiabilityException(vector<Failure> failures)
Expand Down
52 changes: 52 additions & 0 deletions include/tcframe/generator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,45 @@ class BaseGenerator : public TProblem, protected TestCasesCollector {
os->forceMakeDir(testCasesDir);

subtasks = TProblem::getSubtasks();
multipleTestCasesConstraints = TProblem::getMultipleTestCasesConstraints();
testData = getTestData();

bool successful = true;
for (TestGroup* testGroup : testData) {
int testGroupId = testGroup->getId();
logger->logTestGroupIntroduction(testGroupId);

bool testGroupSuccessful = true;

for (int testCaseId = 1; testCaseId <= testGroup->getTestCasesCount(); testCaseId++) {
if (!generateTestCase(testGroupId, testCaseId)) {
testGroupSuccessful = false;
}
}

if (!testGroupSuccessful) {
successful = false;
}

if (testGroupSuccessful && TProblem::isMultipleTestCasesPerFile()) {
string testCaseBaseName = Util::constructTestCaseBaseName(TProblem::getSlug(), testGroupId);
logger->logMultipleTestCasesCombinationIntroduction(testCaseBaseName);

*(TProblem::getMultipleTestCasesCountPointer()) = testGroup->getTestCasesCount();

try {
checkMultipleTestCasesConstraints();
} catch (MultipleTestCasesConstraintsSatisfiabilityException& e) {
logger->logMultipleTestCasesCombinationFailedResult();
logger->logFailures(e.getFailures());
successful = false;
continue;
}

string testCaseBaseFilename = getTestCasesDir() + "/" + testCaseBaseName;
os->combineMultipleTestCases(testCaseBaseFilename, testGroup->getTestCasesCount());

logger->logMultipleTestCasesCombinationOkResult();
}
}

Expand Down Expand Up @@ -174,6 +202,7 @@ class BaseGenerator : public TProblem, protected TestCasesCollector {

vector<TestGroup*> testData;
vector<Subtask*> subtasks;
vector<Constraint*> multipleTestCasesConstraints;

vector<void(BaseGenerator::*)()> testGroupBlocks = {
&BaseGenerator::TestGroup1,
Expand Down Expand Up @@ -279,9 +308,32 @@ class BaseGenerator : public TProblem, protected TestCasesCollector {
}
}

void checkMultipleTestCasesConstraints() {
vector<Constraint*> unsatisfiedConstraints;
for (Constraint* constraint : multipleTestCasesConstraints) {
if (!constraint->isSatisfied()) {
unsatisfiedConstraints.push_back(constraint);
}
}

if (!unsatisfiedConstraints.empty()) {
vector<Failure> failures;
failures.push_back(Failure("Does not satisfy multiple test cases constraints, on:", 0));
for (Constraint* constraint : unsatisfiedConstraints) {
failures.push_back(Failure(constraint->getDescription(), 1));
}

throw MultipleTestCasesConstraintsSatisfiabilityException(failures);
}
}

void generateTestCaseInput(string testCaseInputFilename) {
ostream* testCaseInput = os->openForWriting(testCaseInputFilename);

if (TProblem::isMultipleTestCasesPerFile()) {
*testCaseInput << "1\n";
}

TProblem::beginPrintingFormat(testCaseInput);
TProblem::InputFormat();
TProblem::endPrintingFormat();
Expand Down
16 changes: 16 additions & 0 deletions include/tcframe/logger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ class GeneratorLogger : public Logger {
virtual void logIntroduction() = 0;
virtual void logTestCaseOkResult() = 0;
virtual void logTestCaseFailedResult(string testCaseDescription) = 0;
virtual void logMultipleTestCasesCombinationIntroduction(string testCaseBaseName) = 0;
virtual void logMultipleTestCasesCombinationOkResult() = 0;
virtual void logMultipleTestCasesCombinationFailedResult() = 0;
};

class SubmitterLogger : public Logger {
Expand Down Expand Up @@ -99,6 +102,19 @@ class DefaultGeneratorLogger : public GeneratorLogger {

cout << " Reasons:" << endl;
}

void logMultipleTestCasesCombinationIntroduction(string testCaseBaseName) {
cout << " Combining test cases into a single file (" << testCaseBaseName << ")... ";
}

void logMultipleTestCasesCombinationOkResult() {
cout << "OK" << endl;
}

void logMultipleTestCasesCombinationFailedResult() {
cout << "FAILED" << endl;
cout << " Reasons:" << endl;
}
};

class DefaultSubmitterLogger : public SubmitterLogger {
Expand Down
20 changes: 20 additions & 0 deletions include/tcframe/os.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class OperatingSystem {
virtual void limitExecutionTime(int timeLimitInSeconds) = 0;
virtual void limitExecutionMemory(int memoryLimitInMegabytes) = 0;
virtual ExecutionResult execute(string id, string command, string inputFilename, string outputFilename, string errorFilename) = 0;
virtual void combineMultipleTestCases(string testCaseBaseFilename, int testCasesCount) = 0;
};

class UnixOperatingSystem : public OperatingSystem {
Expand Down Expand Up @@ -120,6 +121,25 @@ class UnixOperatingSystem : public OperatingSystem {
return result;
}

void combineMultipleTestCases(string testCaseBaseFilename, int testCasesCount) {
ostringstream sout;
sout << "echo " << testCasesCount << " > " << testCaseBaseFilename << ".in";
sout << " && touch " << testCaseBaseFilename << ".out";
system(sout.str().c_str());

for (int i = 1; i <= testCasesCount; i++) {
ostringstream sout2;
sout2 << "tail -n +2 " << testCaseBaseFilename << "_" << i << ".in >> " << testCaseBaseFilename << ".in";
sout2 << "&& cat " << testCaseBaseFilename << "_" << i << ".out >> " << testCaseBaseFilename << ".out";
system(sout2.str().c_str());

ostringstream sout3;
sout3 << "rm " << testCaseBaseFilename << "_" << i << ".in ";
sout3 << testCaseBaseFilename << "_" << i << ".out";
system(sout3.str().c_str());
}
}

private:
int timeLimitInSeconds = 0;
int memoryLimitInMegabytes = 0;
Expand Down
21 changes: 21 additions & 0 deletions include/tcframe/problem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ class BaseProblem : protected ConstraintsCollector, protected IOFormatProvider {
return memoryLimit;
}

bool isMultipleTestCasesPerFile() {
return multipleTestCasesCount != nullptr;
}

int* getMultipleTestCasesCountPointer() {
return multipleTestCasesCount;
}

vector<Subtask*> getSubtasks() {
try {
Constraints();
Expand All @@ -88,6 +96,12 @@ class BaseProblem : protected ConstraintsCollector, protected IOFormatProvider {
}
}

vector<Constraint*> getMultipleTestCasesConstraints() {
ConstraintsCollector::setMultipleTestCasesMode();
MultipleTestCasesConstraints();
return ConstraintsCollector::collectMultipleTestCasesConstraints();
}

protected:
virtual ~BaseProblem() { }

Expand All @@ -96,6 +110,8 @@ class BaseProblem : protected ConstraintsCollector, protected IOFormatProvider {
virtual void InputFormat() = 0;
virtual void OutputFormat() = 0;

virtual void MultipleTestCasesConstraints() { }

virtual void Constraints() { throw NotImplementedException(); }
virtual void Subtask1() { throw NotImplementedException(); }
virtual void Subtask2() { throw NotImplementedException(); }
Expand All @@ -120,10 +136,15 @@ class BaseProblem : protected ConstraintsCollector, protected IOFormatProvider {
this->memoryLimit = memoryLimitInMegabytes;
}

void setMultipleTestCasesCount(int& testCasesCount) {
this->multipleTestCasesCount = &testCasesCount;
}

private:
string slug = "problem";
int timeLimit = 0;
int memoryLimit = 0;
int* multipleTestCasesCount = nullptr;

vector<void(BaseProblem::*)()> subtaskBlocks = {
&BaseProblem::Subtask1,
Expand Down
17 changes: 14 additions & 3 deletions include/tcframe/submitter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,24 @@ class Submitter {
logger->logTestGroupIntroduction(testGroupId);
}

for (int testCaseId = 1; testCaseId <= testGroup->getTestCasesCount(); testCaseId++) {
TestCase* testCase = testGroup->getTestCase(testCaseId - 1);
string testCaseName = Util::constructTestCaseName(generator->getSlug(), testGroup->getId(), testCaseId);
if (generator->isMultipleTestCasesPerFile()) {
string testCaseName = Util::constructTestCaseBaseName(generator->getSlug(), testGroupId);
Verdict verdict = submitOnTestCase(testCaseName);

TestCase* testCase = testGroup->getTestCase(0);
for (int subtaskId : testCase->getSubtaskIds()) {
subtaskVerdicts[subtaskId] = max(subtaskVerdicts[subtaskId], verdict);
}
} else {
for (int testCaseId = 1; testCaseId <= testGroup->getTestCasesCount(); testCaseId++) {
TestCase *testCase = testGroup->getTestCase(testCaseId - 1);
string testCaseName = Util::constructTestCaseName(generator->getSlug(), testGroup->getId(),
testCaseId);
Verdict verdict = submitOnTestCase(testCaseName);
for (int subtaskId : testCase->getSubtaskIds()) {
subtaskVerdicts[subtaskId] = max(subtaskVerdicts[subtaskId], verdict);
}
}
}
}

Expand Down
12 changes: 8 additions & 4 deletions include/tcframe/util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,19 @@ class Util {
return result;
}

static string constructTestCaseName(string slug, int testGroupId, int testCaseId) {
static string constructTestCaseBaseName(string slug, int testGroupId) {
if (testGroupId == 0) {
return slug + "_sample_" + toString(testCaseId);
return slug + "_sample";
} else if (testGroupId == -1) {
return slug + "_" + toString(testCaseId);
return slug;
} else {
return slug + "_" + toString(testGroupId) + "_" + toString(testCaseId);
return slug + "_" + toString(testGroupId);
}
}

static string constructTestCaseName(string slug, int testGroupId, int testCaseId) {
return constructTestCaseBaseName(slug, testGroupId) + "_" + toString(testCaseId);
}
};

}
Expand Down
Loading

0 comments on commit 0a6cdd0

Please sign in to comment.