diff --git a/judges/recodex_token_judge/bpplib/algo/lcs.hpp b/judges/recodex_token_judge/bpplib/algo/lcs.hpp index 78f7b543..375173fc 100644 --- a/judges/recodex_token_judge/bpplib/algo/lcs.hpp +++ b/judges/recodex_token_judge/bpplib/algo/lcs.hpp @@ -1,6 +1,6 @@ /* * Author: Martin Krulis -* Last Modification: 24.5.2018 +* Last Modification: 27.6.2019 * License: CC 3.0 BY-NC (http://creativecommons.org/) */ #ifndef BPPLIB_ALGO_LCS_HPP @@ -13,41 +13,180 @@ namespace bpp { + namespace _priv + { + std::pair computeWindow(std::size_t r, std::size_t rowSize, std::size_t maxWindowSize) + { + std::size_t fromI = 0, toI = rowSize; + if (maxWindowSize > 0 && maxWindowSize <= toI) { + fromI = r < (maxWindowSize / 2) ? 0 : r - (maxWindowSize / 2); + toI = std::min(std::max(r + (maxWindowSize / 2) + 1, fromI + maxWindowSize), toI); + if (toI == rowSize) { + fromI = toI - maxWindowSize; + } + } + return std::make_pair(fromI, toI); + } + + + /** + * Internal implementation of LCS algorithm, which founds only the length of the LCS itself. + * The algorithm has a tuning parameter maxWindowSize, which allows to reduce amount of computation + * in exchange for loosing precision (using approximative LCS only). + * The window size limits the width of each row being explored in LCS matrix. + * \tparam RES The result type (must be an integral type). + * \tparam CONTAINER Class holding a sequence. The class must have size() method + * and the comparator must be able to get values from the container based on their indices. + * \tparam COMPARATOR Comparator class holds a static method compare(seq1, i1, seq2, i2) -> bool. + * I.e., the comparator is also responsible for fetching values from the seq. containers. + */ + template + RES longest_common_subsequence_length(const CONTAINER& sequence1, const CONTAINER& sequence2, + COMPARATOR comparator, std::size_t maxWindowSize = 0) + { + if (sequence1.size() == 0 || sequence2.size() == 0) return (RES)0; + + // Make sure in seq1 is the longer sequence ... + const CONTAINER& seq1 = sequence1.size() >= sequence2.size() ? sequence1 : sequence2; + const CONTAINER& seq2 = sequence1.size() < sequence2.size() ? sequence1 : sequence2; + + std::vector row((std::size_t)seq2.size()); + std::size_t rows = (std::size_t)seq1.size(); + + // Dynamic programming - matrix traversal that keeps only the last row. + for (std::size_t r = 0; r < rows; ++r) { + RES lastUpperLeft = 0, lastLeft = 0; + auto window = computeWindow(r, row.size(), maxWindowSize); + for (std::size_t i = window.first; i < window.second; ++i) { + RES upper = row[i]; + row[i] = + (comparator(seq1, r, seq2, i)) + ? lastUpperLeft + 1 + : std::max(lastLeft, upper); + lastLeft = row[i]; + lastUpperLeft = upper; + } + } + + return row.back(); + } + + + /** + * Internal implementation of longest common subsequence algorithm which founds exactly one common subsequence. + * \tparam RES The result type (must be an integral type). + * \tparam CONTAINER Class holding the sequence. The class must have size() method + * and the comparator must be able to get values from the container based on their indices. + * \tparam COMPARATOR Comparator class holds a static method compare(seq1, i1, seq2, i2) -> bool. + * I.e., the comparator is also responsible for fetching values from the seq. containers. + */ + template + void longest_common_subsequence(const CONTAINER& sequence1, const CONTAINER& sequence2, + std::vector>& common, COMPARATOR comparator, std::size_t maxWindowSize = 0) + { + struct Node { + std::pair previous; + IDX length; + bool match; + public: + Node() : length(0), match(false) {} + }; + + + class NodeMatrix + { + private: + std::size_t mSize1; + std::vector mNodes; + public: + NodeMatrix(std::size_t size1, std::size_t size2) : mSize1(size1 + 1) + { + mNodes.resize((size1 + 1) * (size2 + 1)); + for (std::size_t i = 1; i <= size1; ++i) { + at(i, 0).previous.first = 1; + } + for (std::size_t i = 1; i <= size2; ++i) { + at(0, i).previous.second = 1; + } + } + + Node& at(std::size_t c, std::size_t r) + { + return mNodes[r * mSize1 + c]; + } + }; + + + common.clear(); + if (sequence1.size() == 0 || sequence2.size() == 0) return; + + const std::size_t size1 = sequence1.size(); + const std::size_t size2 = sequence2.size(); + NodeMatrix matrix(size1, size2); + + // Fill in the LCS matrix by dynamic programming + for (std::size_t r = 0; r < size2; ++r) { // iterate over rows + auto window = computeWindow(r, size1, maxWindowSize); + for (std::size_t c = window.first; c < window.second; ++c) { // iterate over cols + bool match = matrix.at(c+1, r+1).match = comparator(sequence1, c, sequence2, r); + + if (match) { + // Matching tokens should prolong the sequence... + matrix.at(c + 1, r + 1).length = matrix.at(c, r).length + 1; + matrix.at(c + 1, r + 1).previous.first = 1; + matrix.at(c + 1, r + 1).previous.second = 1; + } + else { + IDX leftLength = matrix.at(c, r + 1).length; + IDX upperLength = matrix.at(c + 1, r).length; + if (leftLength >= upperLength) { + matrix.at(c + 1, r + 1).previous.first = 1; + matrix.at(c + 1, r + 1).length = leftLength; + } + else { + matrix.at(c + 1, r + 1).previous.second = 1; + matrix.at(c + 1, r + 1).length = upperLength; + } + } + } + } + + // Collect the result path from the matrix... + std::size_t c = size1; + std::size_t r = size2; + while (c > 0 && r > 0) { + const Node& node = matrix.at(c, r); + if (node.match) { + common.push_back(std::make_pair(c - 1, r - 1)); + } + + if (node.previous.first + node.previous.second > 0) { + c -= node.previous.first; + r -= node.previous.second; + } + else { // let's make sure we will not get stuck (if approx. version of LCS is running) + if (c >= r) --c; + if (c <= r) --r; + } + } + + // Fix the result (since it was collected backwards)... + std::reverse(common.begin(), common.end()); + } + } + /** * Implements a longest common subsequence algorithm, which founds only the length of the LCS itself. * \tparam RES The result type (must be an integral type). * \tparam CONTAINER Class holding a sequence. The class must have size() method * and the comparator must be able to get values from the container based on their indices. - * \tparma COMPARATOR Comparator class holds a static method compare(seq1, i1, seq2, i2) -> bool. + * \tparam COMPARATOR Comparator class holds a static method compare(seq1, i1, seq2, i2) -> bool. * I.e., the comparator is also responsible for fetching values from the seq. containers. */ template RES longest_common_subsequence_length(const CONTAINER &sequence1, const CONTAINER &sequence2, COMPARATOR comparator) { - if (sequence1.size() == 0 || sequence2.size() == 0) return (RES)0; - - // Make sure in seq1 is the longer sequence ... - const CONTAINER &seq1 = sequence1.size() >= sequence2.size() ? sequence1 : sequence2; - const CONTAINER &seq2 = sequence1.size() < sequence2.size() ? sequence1 : sequence2; - - std::vector row((std::size_t)seq2.size()); - std::size_t rows = (std::size_t)seq1.size(); - - // Dynamic programming - matrix traversal that keeps only the last row. - for (std::size_t r = 0; r < rows; ++r) { - RES lastUpperLeft = 0, lastLeft = 0; - for (std::size_t i = 0; i < row.size(); ++i) { - RES upper = row[i]; - row[i] = - (comparator(seq1, r, seq2, i)) - ? lastUpperLeft + 1 - : std::max(lastLeft, upper); - lastLeft = row[i]; - lastUpperLeft = upper; - } - } - - return row.back(); + return _priv::longest_common_subsequence_length(sequence1, sequence2, comparator); } @@ -65,82 +204,18 @@ namespace bpp /** - * Implements a longest common subsequence algorithm which founds exactly one common subsequence. - * \tparam RES The result type (must be an integral type). - * \tparam CONTAINER Class holding the sequence. The class must have size() method - * and the comparator must be able to get values from the container based on their indices. - * \tparma COMPARATOR Comparator class holds a static method compare(seq1, i1, seq2, i2) -> bool. - * I.e., the comparator is also responsible for fetching values from the seq. containers. - */ + * Implements a longest common subsequence algorithm which founds exactly one common subsequence. + * \tparam RES The result type (must be an integral type). + * \tparam CONTAINER Class holding the sequence. The class must have size() method + * and the comparator must be able to get values from the container based on their indices. + * \tparam COMPARATOR Comparator class holds a static method compare(seq1, i1, seq2, i2) -> bool. + * I.e., the comparator is also responsible for fetching values from the seq. containers. + */ template void longest_common_subsequence(const CONTAINER &sequence1, const CONTAINER &sequence2, std::vector> &common, COMPARATOR comparator) { - struct Node { - std::pair previous; - IDX length; - bool match; - }; - - common.clear(); - if (sequence1.size() == 0 || sequence2.size() == 0) return; - - const std::size_t size1 = sequence1.size(); - const std::size_t size2 = sequence2.size(); - - // Prepare vector representing the LCS matrix ... - std::vector matrix((size1 + 1) * (size2 + 1)); - for (std::size_t i = 0; i < size1; ++i) { - matrix[i + 1].previous.first = 1; - } - for (std::size_t i = 0; i < size2; ++i) { - matrix[(i + 1)*(size1 + 1)].previous.second = 1; - } - - // Fill in the LCS matrix by dynamic programming - std::size_t i = size1 + 2; // current position in matrix (i == (c+1)*(size1+1) + (r+1)) - for (std::size_t r = 0; r < size2; ++r) { // iterate over rows - for (std::size_t c = 0; c < size1; ++c) { // iterate over cols - matrix[i].match = comparator(sequence1, c, sequence2, r); - - if (matrix[i].match) { - // Matching tokens should prolong the sequence... - matrix[i].length = matrix[i - size1 - 2].length + 1; - matrix[i].previous.first = 1; - matrix[i].previous.second = 1; - } - else { - IDX leftLength = matrix[i - 1].length; - IDX upperLength = matrix[i - size1 - 1].length; - if (leftLength >= upperLength) { - matrix[i].previous.first = 1; - matrix[i].length = leftLength; - } - else { - matrix[i].previous.second = 1; - matrix[i].length = upperLength; - } - } - ++i; - } - ++i; // skip the first (padding) column - } - - // Collect the result path from the matrix... - std::size_t c = size1; - std::size_t r = size2; - while (c > 0 && r > 0) { - const Node &node = matrix[r * (size1 + 1) + c]; - if (node.match) { - common.push_back(std::make_pair(c - 1, r - 1)); - } - - c -= node.previous.first; - r -= node.previous.second; - } - - // Fix the result (since it was collected backwards)... - std::reverse(common.begin(), common.end()); + return _priv::longest_common_subsequence(sequence1, sequence2, common, comparator); } @@ -155,6 +230,71 @@ namespace bpp } + /* + * Approximate LCS + */ + + /** + * Implements approximative version of LCS algorithm, which founds only the length of the LCS itself. + * The algorihm may not find the longest subsequence, so the result may be lower or equal to actual LCS, + * but it requires much less time as it does not explore the all possible pairings. + * \tparam RES The result type (must be an integral type). + * \tparam CONTAINER Class holding a sequence. The class must have size() method + * and the comparator must be able to get values from the container based on their indices. + * \tparam COMPARATOR Comparator class holds a static method compare(seq1, i1, seq2, i2) -> bool. + * I.e., the comparator is also responsible for fetching values from the seq. containers. + */ + template + RES longest_common_subsequence_approx_length(const CONTAINER& sequence1, const CONTAINER& sequence2, + COMPARATOR comparator, std::size_t maxWindowSize = 31) + { + return _priv::longest_common_subsequence_length(sequence1, sequence2, comparator, maxWindowSize); + } + + + // Only an overload that uses default comparator. + template + RES longest_common_subsequence_approx_length(const CONTAINER& sequence1, const CONTAINER& sequence2, std::size_t maxWindowSize = 31) + { + return longest_common_subsequence_approx_length(sequence1, sequence2, + [](const CONTAINER& seq1, std::size_t i1, const CONTAINER& seq2, std::size_t i2) -> bool { + return seq1[i1] == seq2[i2]; + }, + maxWindowSize + ); + } + + + /** + * Implements an approximative version of the longest common subsequence algorithm which founds exactly one common subsequence. + * The algorihm may not find the longest subsequence, so the result may be shorter to actual LCS, + * but it requires much less time as it does not explore the all possible pairings. + * \tparam RES The result type (must be an integral type). + * \tparam CONTAINER Class holding the sequence. The class must have size() method + * and the comparator must be able to get values from the container based on their indices. + * \tparam COMPARATOR Comparator class holds a static method compare(seq1, i1, seq2, i2) -> bool. + * I.e., the comparator is also responsible for fetching values from the seq. containers. + */ + template + void longest_common_subsequence_approx(const CONTAINER& sequence1, const CONTAINER& sequence2, + std::vector>& common, COMPARATOR comparator, std::size_t maxWindowSize = 31) + { + return _priv::longest_common_subsequence(sequence1, sequence2, common, comparator, maxWindowSize); + } + + + // Only an overload that uses default comparator. + template + void longest_common_subsequence_approx(const CONTAINER& sequence1, const CONTAINER& sequence2, + std::vector>& common, std::size_t maxWindowSize = 31) + { + longest_common_subsequence_approx(sequence1, sequence2, common, + [](const CONTAINER& seq1, std::size_t i1, const CONTAINER& seq2, std::size_t i2) -> bool { + return seq1[i1] == seq2[i2]; + }, + maxWindowSize); + } + } #endif diff --git a/judges/recodex_token_judge/bpplib/cli/args.hpp b/judges/recodex_token_judge/bpplib/cli/args.hpp index 72c2909d..fc756d4a 100644 --- a/judges/recodex_token_judge/bpplib/cli/args.hpp +++ b/judges/recodex_token_judge/bpplib/cli/args.hpp @@ -151,7 +151,7 @@ namespace bpp * \param mandatory Flag that indicates whether the argument must be present on commandline. */ ArgBase(const std::string &name, const std::string &comment, bool mandatory) - : mName(name), mComment(comment), mMandatory(mandatory), mPresent(false) + : args(nullptr), mName(name), mComment(comment), mMandatory(mandatory), mPresent(false) { if (mName.empty()) throw(ArgumentException() << "Argument name must not be empty."); diff --git a/judges/recodex_token_judge/comparator.hpp b/judges/recodex_token_judge/comparator.hpp index 72a58d0b..9522dfb4 100644 --- a/judges/recodex_token_judge/comparator.hpp +++ b/judges/recodex_token_judge/comparator.hpp @@ -11,10 +11,11 @@ #include #include #include + #include #include #include - +#include /** * Try to parse long int out of a string. Return true if the string contains only an integer, false otherwise. @@ -215,6 +216,7 @@ template &mTokenComparator; ///< Token comparator used for comparing tokens on the lines. bool mShuffledTokens; ///< Whether the tokens on each line may be in arbitrary order. + std::size_t mApproxLcsMaxWindow; ///< Tuning (performance) parameter, when should LCS fall back to approx version /** * Log one error of unordered token comparison (a token is superfluous or missing). @@ -541,6 +543,52 @@ template &comparator = mTokenComparator; + std::size_t i1 = 0, i2 = 0, errors = 0; + + // Log first three mismatches (using direct pairing) + while (i1 < line1.size() && i2 < line2.size() && errors < 3) { + if (!comparator.compare(line1.getTokenCStr(i1), + line1.getTokenLength(i1), + line2.getTokenCStr(i2), + line2.getTokenLength(i2))) { + logMismatchError(line1[i1], line1.getTokenAsString(i1), line2[i2], line2.getTokenAsString(i2)); + ++errors; + } + ++i1; + ++i2; + } + + while (i1 < line1.size() && errors < 3) { + // Missing tokens (present in correct file, but missing in results). + logOrderedError(line1[i1], line1.getTokenAsString(i1), "-"); + ++errors; + ++i1; + } + + while (i2 < line2.size() && errors < 3) { + // Missing tokens (present in correct file, but missing in results). + logOrderedError(line2[i2], line2.getTokenAsString(i2), "+"); + ++errors; + ++i2; + } + + if (i1 < line1.size() || i2 < line2.size()) { + bpp::log().error() << " ..."; + } + } + + /** * Apply LCS algorithm to find the best matching between the two lines * and determine the error as the number of tokens not present in the common subequence. @@ -554,58 +602,81 @@ template &comparator = mTokenComparator; std::size_t prefixLen = getCommonLinePrefixLength(line1, line2, comparator); - if (prefixLen == line1.size() && prefixLen == line2.size()) return 0; // both lines are identical + if (prefixLen == line1.size() && prefixLen == line2.size()) return 0; // both lines are identical std::size_t suffixLen = getCommonLineSuffixLength(line1, line2, comparator); lineview_t lineView1(line1, prefixLen, line1.size() - prefixLen - suffixLen); lineview_t lineView2(line2, prefixLen, line2.size() - prefixLen - suffixLen); if (LOGGING) { - std::vector> lcs; - bpp::longest_common_subsequence(lineView1, - lineView2, - lcs, - [&comparator](const lineview_t &line1, std::size_t i1, const lineview_t &line2, std::size_t i2) { - return comparator.compare( - line1.getTokenCStr(i1), line1.getTokenLength(i1), line2.getTokenCStr(i2), line2.getTokenLength(i2)); - }); - - // If there are no errors, return immediately. - result_t res = (result_t)(lineView1.size() - lcs.size() + lineView2.size() - lcs.size()); - if (res == 0) return res; - bpp::log().error() << "-" << line1.lineNumber() << "/+" << line2.lineNumber() << ":"; + result_t res; - std::size_t c = 0; - std::size_t r = 0; - for (auto &&it : lcs) { - logOrderedErrors(lineView1, lineView2, c, r, it.first, it.second); + if (mApproxLcsMaxWindow > 0 && std::min(lineView1.size(), lineView2.size()) > mApproxLcsMaxWindow) { + res = (result_t)( + lineView1.size() + lineView2.size()); // we do not compute lcs anymore (this is worst case result) + logApproxErrors(lineView1, lineView2); + } else { + std::vector> lcs; + bpp::longest_common_subsequence(lineView1, + lineView2, + lcs, + [&comparator](const lineview_t &line1, std::size_t i1, const lineview_t &line2, std::size_t i2) { + return comparator.compare(line1.getTokenCStr(i1), + line1.getTokenLength(i1), + line2.getTokenCStr(i2), + line2.getTokenLength(i2)); + }); + + // If there are no errors, return immediately. + res = (result_t)(lineView1.size() - lcs.size() + lineView2.size() - lcs.size()); + assert(res > 0); + + std::size_t c = 0; + std::size_t r = 0; + for (auto &&it : lcs) { + logOrderedErrors(lineView1, lineView2, c, r, it.first, it.second); + + // Skip the matched pair ... + ++c; + ++r; + } - // Skip the matched pair ... - ++c; - ++r; + // Log trailing tokens after last matched pair. + logOrderedErrors(lineView1, lineView2, c, r, lineView1.size(), lineView2.size()); } - // Log trailing tokens after last matched pair. - logOrderedErrors(lineView1, lineView2, c, r, lineView1.size(), lineView2.size()); - bpp::log().error() << "\n"; return res; } else { - std::size_t lcs = bpp::longest_common_subsequence_length(lineView1, - lineView2, - [&comparator](const lineview_t &line1, std::size_t i1, const lineview_t &line2, std::size_t i2) { - return comparator.compare( - line1.getTokenCStr(i1), line1.getTokenLength(i1), line2.getTokenCStr(i2), line2.getTokenLength(i2)); - }); + std::size_t lcs = + (mApproxLcsMaxWindow > 0 && std::min(lineView1.size(), lineView2.size()) > mApproxLcsMaxWindow) ? + bpp::longest_common_subsequence_approx_length(lineView1, + lineView2, + [&comparator](const lineview_t &line1, std::size_t i1, const lineview_t &line2, std::size_t i2) { + return comparator.compare(line1.getTokenCStr(i1), + line1.getTokenLength(i1), + line2.getTokenCStr(i2), + line2.getTokenLength(i2)); + }, + mApproxLcsMaxWindow) : + bpp::longest_common_subsequence_length(lineView1, + lineView2, + [&comparator](const lineview_t &line1, std::size_t i1, const lineview_t &line2, std::size_t i2) { + return comparator.compare(line1.getTokenCStr(i1), + line1.getTokenLength(i1), + line2.getTokenCStr(i2), + line2.getTokenLength(i2)); + }); + return (result_t)(lineView1.size() - lcs + lineView2.size() - lcs); } } public: - LineComparator(TokenComparator &tokenComparator, bool shuffledTokens) - : mTokenComparator(tokenComparator), mShuffledTokens(shuffledTokens) + LineComparator(TokenComparator &tokenComparator, bool shuffledTokens, std::size_t approxLcsMaxWindow) + : mTokenComparator(tokenComparator), mShuffledTokens(shuffledTokens), mApproxLcsMaxWindow(approxLcsMaxWindow) { } diff --git a/judges/recodex_token_judge/recodex-token-judge.cpp b/judges/recodex_token_judge/recodex-token-judge.cpp index 5435437a..7d46da98 100644 --- a/judges/recodex_token_judge/recodex-token-judge.cpp +++ b/judges/recodex_token_judge/recodex-token-judge.cpp @@ -51,6 +51,8 @@ int main(int argc, char *argv[]) args.registerArg( bpp::make_unique("shuffled-lines", "Lines may appear in any order.")); args.getArg("shuffled-lines").conflictsWith("ignore-line-ends"); + args.registerArg(bpp::make_unique( + "token-lcs-approx-max-window", "Tuning parameter for approx LCS for comparing lines (0 = always full LCS).", false, 11, 0, 255)); // Log args args.registerArg( @@ -92,7 +94,9 @@ int main(int argc, char *argv[]) args.getArgBool("numeric").getValue(), args.getArgFloat("float-tolerance").getValue()); - LineComparator<> lineComparator(tokenComparator, args.getArgBool("shuffled-tokens").getValue()); + LineComparator<> lineComparator(tokenComparator, + args.getArgBool("shuffled-tokens").getValue(), + (std::size_t)args.getArgInt("token-lcs-approx-max-window").getValue()); // Create main judge and execute it ... diff --git a/judges/recodex_token_judge/tests/14-approx.bats b/judges/recodex_token_judge/tests/14-approx.bats new file mode 100644 index 00000000..e1a2e4b8 --- /dev/null +++ b/judges/recodex_token_judge/tests/14-approx.bats @@ -0,0 +1,15 @@ +#!/usr/bin/env bats + +load bats-shared + +@test "approx" { + run $EXE_FILE --token-lcs-approx-max-window 4 $CORRECT_FILE $RESULT_FILE + [ "$status" -eq 1 ] + echo "$output" | diff -abB - "${ERROR_FILE}1" +} + +@test "approx (negative test)" { + run $EXE_FILE --token-lcs-approx-max-window 11 $CORRECT_FILE $RESULT_FILE + [ "$status" -eq 1 ] + echo "$output" | diff -abB - "${ERROR_FILE}2" +} diff --git a/judges/recodex_token_judge/tests/14.correct.in b/judges/recodex_token_judge/tests/14.correct.in new file mode 100644 index 00000000..058f76d5 --- /dev/null +++ b/judges/recodex_token_judge/tests/14.correct.in @@ -0,0 +1,3 @@ +1 2 3 4 5 6 7 8 9 +a b c d e f g h i j k l m n o p q r s t u v w x y z a b c 1 2 3 4 d e f g h i j k l m n o p q r s t u v w x y z +aa bb cc dd ee ff gg hh ii 11 22 33 44 55 66 77 88 99 diff --git a/judges/recodex_token_judge/tests/14.error.out1 b/judges/recodex_token_judge/tests/14.error.out1 new file mode 100644 index 00000000..41054895 --- /dev/null +++ b/judges/recodex_token_judge/tests/14.error.out1 @@ -0,0 +1,6 @@ +0 +-1: 1 2 3 4 5 6 7 8 9 ++1: 1 a 2 b 3 c 4 d 5 e 6 f 7 g 8 h 9 i +-2/+2: (approx) [53]a != [53]1 [55]b != [55]a [57]c != [57]2 ... +-3: aa bb cc dd ee ff gg hh ii 11 22 33 44 55 66 77 88 99 ++3: 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff gg hh ii diff --git a/judges/recodex_token_judge/tests/14.error.out2 b/judges/recodex_token_judge/tests/14.error.out2 new file mode 100644 index 00000000..74cc9175 --- /dev/null +++ b/judges/recodex_token_judge/tests/14.error.out2 @@ -0,0 +1,6 @@ +0 +-1: 1 2 3 4 5 6 7 8 9 ++1: 1 a 2 b 3 c 4 d 5 e 6 f 7 g 8 h 9 i +-2/+2: +[53]1 +[57]2 +[61]3 -[59]1 -[61]2 -[63]3 +-3: aa bb cc dd ee ff gg hh ii 11 22 33 44 55 66 77 88 99 ++3: 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff gg hh ii diff --git a/judges/recodex_token_judge/tests/14.result.in b/judges/recodex_token_judge/tests/14.result.in new file mode 100644 index 00000000..bc665d55 --- /dev/null +++ b/judges/recodex_token_judge/tests/14.result.in @@ -0,0 +1,3 @@ +1 a 2 b 3 c 4 d 5 e 6 f 7 g 8 h 9 i +a b c d e f g h i j k l m n o p q r s t u v w x y z 1 a 2 b 3 c 4 d e f g h i j k l m n o p q r s t u v w x y z +11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff gg hh ii