Skip to content

Commit

Permalink
Support multiple output formats (#141)
Browse files Browse the repository at this point in the history
Resolve #123
  • Loading branch information
fushar committed May 29, 2017
1 parent 2897c4e commit d67e645
Show file tree
Hide file tree
Showing 11 changed files with 300 additions and 70 deletions.
9 changes: 7 additions & 2 deletions docs/api-ref/api-ref.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,18 @@ Defines the input format. It is mandatory.

virtual void BeforeOutputFormat() {}

Executed right before the produced output is validated against the output format. See :ref:`io-formats_before-output-format` for more details.
Executed right before the produced output is validated against the output format(s). See :ref:`io-formats_before-output-format` for more details.

.. sourcecode:: cpp

virtual void OutputFormat() {}

Defines the output format. It is optional; if not implemented, then the output will not be validated.
virtual void OutputFormat1() {}
virtual void OutputFormat2() {}
// ...
virtual void OutputFormat5() {}

Defines the possible output format(s). If there is only one output format, only ``OutputFormat()`` can be specified, otherwise multiple ``OutputFormatX()`` should be specified.

**Defining format**

Expand Down
47 changes: 37 additions & 10 deletions docs/topic-guides/io-formats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,41 @@ You can declare a helper variable, e.g. ``update_count``, and have ``LINES(answe
LINES(answers) % SIZE(update_count);
}

----

.. _io-formats_multiple-output-formats:

Multiple output formats
-----------------------

Sometimes, there are more than one possible output formats. For example: "If there is no valid solution, output a single line containing -1. Otherwise, the first line of the output should contain **K**, followed by a line containing **K** space-separated integers representing the answer."

This can be accomplished using ``OutputFormatX()``, where ``X`` is the output format number. A test case output is valid if it conforms to at least one of the output formats.

.. note::

As of this version, you can define up to 5 output formats: **OutputFormat1()** .. **OutputFormat5()**.

The above example could be implemented as follows.

.. sourcecode:: cpp

int impossible;

int K;
vector<int> answers;

void OutputFormat1() {
LINE(impossible);
}

void OutputFormat2() {
LINE(K);
LINE(answers % SIZE(K));
}

----

Notes
-----

Expand All @@ -139,13 +174,5 @@ Constants in I/O segments

As a workaround, just create an input variable and initialize it to ``BEGIN``.

Complex conditional I/O format that can't be handled by jagged vectors
There is **NO** known general workaround yet. We're still working on designing how to handle complex format.

However, there are workarounds for simple cases, for example:

"Output the required sum, or the string ``IMPOSSIBLE`` if there is no solution."

In this case, you can just use a string as the output variable. The downside is that it is not type-safe; for example, the generation won't fail if the reference solution mistakenly output an invalid string such as ``123abc``.

However, a last resort for a workaround does exist for output format. If you have complex output format, you can just omit the method ``OutputFormat()`` altogether and your solution's output won't be checked at all for validity.
Complex conditional I/O format that can't be handled by jagged vectors/raw line(s)
As a workaround, if you have a very complex output format, you can just omit the methods ``OutputFormat()``/``OutputFormatX()`` altogether and your solution's output won't be checked at all for validity.
31 changes: 29 additions & 2 deletions include/tcframe/io_manipulator/IOManipulator.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#pragma once

#include <istream>
#include <ostream>
#include <string>
#include <vector>

#include "GridIOSegmentManipulator.hpp"
Expand All @@ -12,6 +15,7 @@

using std::istream;
using std::ostream;
using std::string;
using std::vector;

namespace tcframe {
Expand All @@ -35,9 +39,32 @@ class IOManipulator {
}

virtual void parseOutput(istream* in) {
if (!ioFormat_.outputFormat().empty()) {
if (!ioFormat_.outputFormats().empty()) {
ioFormat_.beforeOutputFormat()();
parse(ioFormat_.outputFormat(), in);

long long initialPos = in->tellg();
bool successful = false;
string errorMessage;
for (const IOSegments& outputFormat : ioFormat_.outputFormats()) {
try {
parse(outputFormat, in);
successful = true;
break;
} catch (runtime_error& e) {
in->clear();
in->seekg(initialPos);
if (errorMessage.empty()) {
errorMessage = e.what();
}
}
}

if (!successful) {
if (ioFormat_.outputFormats().size() == 1) {
throw runtime_error(errorMessage);
}
throw runtime_error("Test case output does not conform to any of the output formats");
}
}
}

Expand Down
35 changes: 32 additions & 3 deletions include/tcframe/spec/core/BaseProblemSpec.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ class BaseProblemSpec
protected MultipleTestCasesConfigBuilder,
protected ConstraintSuiteBuilder {
private:
vector<void(BaseProblemSpec::*)()> outputFormats_ = {
&BaseProblemSpec::OutputFormat1,
&BaseProblemSpec::OutputFormat2,
&BaseProblemSpec::OutputFormat3,
&BaseProblemSpec::OutputFormat4,
&BaseProblemSpec::OutputFormat5};

vector<void(BaseProblemSpec::*)()> subtasks_ = {
&BaseProblemSpec::Subtask1,
&BaseProblemSpec::Subtask2,
Expand Down Expand Up @@ -56,8 +63,25 @@ class BaseProblemSpec
IOFormatBuilder::setBeforeOutputFormat([this] {
BeforeOutputFormat();
});
IOFormatBuilder::prepareForOutputFormat();
OutputFormat();
try {
IOFormatBuilder::newOutputFormat();
OutputFormat();
for (auto outputFormat : outputFormats_) {
try {
(this->*outputFormat)();
throw runtime_error("If OutputFormat() is specified, no other OutputFormatX() can be specified");
} catch (NotImplementedException&) {}
}
} catch (NotImplementedException&) {
for (auto outputFormat : outputFormats_) {
try {
(this->*outputFormat)();
IOFormatBuilder::newOutputFormat();
} catch (NotImplementedException&) {
break;
}
}
}
return IOFormatBuilder::build();
}

Expand Down Expand Up @@ -95,7 +119,12 @@ class BaseProblemSpec
protected:
virtual void InputFormat() = 0;
virtual void BeforeOutputFormat() {}
virtual void OutputFormat() {}
virtual void OutputFormat() {throw NotImplementedException();}
virtual void OutputFormat1() {throw NotImplementedException();}
virtual void OutputFormat2() {throw NotImplementedException();}
virtual void OutputFormat3() {throw NotImplementedException();}
virtual void OutputFormat4() {throw NotImplementedException();}
virtual void OutputFormat5() {throw NotImplementedException();}
virtual void StyleConfig() {}
virtual void GradingConfig() {}
virtual void MultipleTestCasesConfig() {}
Expand Down
28 changes: 22 additions & 6 deletions include/tcframe/spec/io/IOFormat.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ struct IOFormat {
private:
IOSegments inputFormat_;
function<void()> beforeOutputFormat_;
IOSegments outputFormat_;
vector<IOSegments> outputFormats_;

public:
const IOSegments& inputFormat() const {
Expand All @@ -36,12 +36,12 @@ struct IOFormat {
return beforeOutputFormat_;
}

const IOSegments& outputFormat() const {
return outputFormat_;
const vector<IOSegments>& outputFormats() const {
return outputFormats_;
}

bool operator==(const IOFormat& o) const {
return equals(inputFormat_, o.inputFormat_) && equals(outputFormat_, o.outputFormat_);
return equals(inputFormat_, o.inputFormat_) && equals(outputFormats_, o.outputFormats_);
}

private:
Expand All @@ -56,6 +56,18 @@ struct IOFormat {
}
return true;
}

bool equals(const vector<IOSegments>& a, const vector<IOSegments>& b) const {
if (a.size() != b.size()) {
return false;
}
for (int i = 0; i < a.size(); i++) {
if (!equals(a[i], b[i])) {
return false;
}
}
return true;
}
};

class IOFormatBuilder {
Expand All @@ -77,9 +89,10 @@ class IOFormatBuilder {
subject_.beforeOutputFormat_ = beforeOutputFormat;
}

void prepareForOutputFormat() {
void newOutputFormat() {
addLastSegment();
currentFormat_ = &subject_.outputFormat_;
subject_.outputFormats_.push_back(IOSegments());
currentFormat_ = &subject_.outputFormats_.back();
}

LineIOSegmentBuilder& newLineIOSegment() {
Expand Down Expand Up @@ -119,6 +132,9 @@ class IOFormatBuilder {

IOFormat build() {
addLastSegment();
if (!subject_.outputFormats_.empty() && subject_.outputFormats_.back().empty()) {
subject_.outputFormats_.pop_back();
}
return move(subject_);
}

Expand Down
16 changes: 12 additions & 4 deletions test/ete/resources/normal-complex-formats/solution.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
#include <cstdio>

int A, B;
int N;
char buf[100];

int main() {
printf("yes\n");
printf("lorem ipsum\n");
printf(" dolor sit amet! \n");
gets(buf);

scanf("%d", &N);
if (N == 1) {
printf("no it's impossible\n");
} else {
printf("yes\n");
printf("lorem ipsum\n");
printf(" dolor sit amet! \n");
}
}
21 changes: 20 additions & 1 deletion test/ete/resources/normal-complex-formats/spec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class ProblemSpec : public BaseProblemSpec {
vector<int> X, Y;
vector<vector<int>> M;

vector<string> res_impossible;
string res;
vector<string> answers;

Expand All @@ -23,7 +24,11 @@ class ProblemSpec : public BaseProblemSpec {
GRID(M) % SIZE(2, 3);
}

void OutputFormat() {
void OutputFormat1() {
LINE(res_impossible % SIZE(3));
}

void OutputFormat2() {
LINE(res);
RAW_LINES(answers);
}
Expand Down Expand Up @@ -57,7 +62,21 @@ class TestSpec : public BaseTestSpec<ProblemSpec> {
});
}

void SampleTestCase2() {
Input({
"[BEGIN INPUT]",
"1",
"3 5",
"7 7 7",
"8 8 8"
});
Output({
"no it's impossible"
});
}

void TestCases() {
CASE(S = "[BEGIN INPUT]", N = 1, A = {10, 20}, X = {}, Y = {}, M = { {1, 2, 3}, {4, 5, 6} });
CASE(S = "[BEGIN INPUT]", N = 3, A = {10, 20}, X = {0, 0}, Y = {1, 1}, M = { {1, 2, 3}, {4, 5, 6} });
}
};
6 changes: 5 additions & 1 deletion test/ete/tcframe/GenerationEteTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ TEST_F(GenerationEteTests, Normal_ComplexFormats) {
EXPECT_THAT(ls("test-ete/normal-complex-formats/tc"), UnorderedElementsAre(
"normal-complex-formats_sample_1.in",
"normal-complex-formats_sample_1.out",
"normal-complex-formats_sample_2.in",
"normal-complex-formats_sample_2.out",
"normal-complex-formats_1.in",
"normal-complex-formats_1.out"
"normal-complex-formats_1.out",
"normal-complex-formats_2.in",
"normal-complex-formats_2.out"
));
}

Expand Down

0 comments on commit d67e645

Please sign in to comment.