Which library. A new checker is written with eolymp.h, as on this page. If the problemalready has a PROGRAM checker, read it first with get_checker: one that includestestlib.h or calls registerTestlibCmd is changed in testlib — followWrite a checker with testlib.h — and so is a new one therequester explicitly asked to be written with testlib. For an interactive problem the checkerfollows its interactor: see Write an interactor. The rule, andwhy, is in eolymp.h reference.
A program that decides what a contestant's output is worth on one test: accepted, a wronganswer, a partial score, or a jury error — the problem is broken, not the contestant. It seesthree files:
stream | is | trust |
|---|---|---|
| the test input | trusted — the validator already vetted it |
| the contestant's output | hostile — arbitrary bytes, assume nothing |
| the jury's answer | verify it too — see §4 |
Write a PROGRAM checker only when correctness genuinely needs logic — several validanswers, a constructed object to verify, or partial credit. Reach for a built-in first:
LINES — byte-for-byte line comparison. Trailing spaces and tabs on each line, andempty lines at the end of the file, are ignored; everything else must match exactly.
TOKENS — compares word by word or number by number. Numbers are compared with arelative tolerance; words case-insensitively unless case_sensitive is set. Itfails with a system failure on a single token over 64 KB.
QUERY_RESULTS — for per-query output.
A statement promising "any case is accepted" is only true under TOKENS withcase_sensitive off, or a checker that folds case.
#include <eolymp.h>
int main(int argc, char** argv) {
eo::checker c(argc, argv);
long long expected = c.jury.read_long(eo::any, "sum");
long long found = c.output.read_long(eo::any, "sum");
if (found != expected) eo::wrong("the sum is {}, not {}", expected, found);
eo::accept("the sum is {}", found);
}eo::checker c(argc, argv) finds the three files, reads the test's cost and takes over thelog.
A read that fails on c.output is a wrong answer; on c.jury or c.input it is a juryerror. You never choose — the stream decides.
After a verdict that accepts or scores, anything but whitespace left in the output is awrong answer, "extra output after the answer". c.output.trailing(eo::ignore) allows itwhen the task does.
Every path must end in a verdict. Returning from main without one is a jury error.
That shape is enough when the answer is one value. For anything composite, use §4.
Runtime: store it as cpp:20-gnu14, type PROGRAM, with no files entry — theheader is in the judge's runtime. The judge gives it 10 s of wall time.
call | exit | result |
|---|---|---|
| 0 | ACCEPTED, the test's full cost |
| 1 | WRONG_ANSWER |
| 7 | PARTIALLY_CORRECT, |
| as | as |
| 3 | the problem is broken — the run fails as a system error |
| 1 | a wrong answer blamed on that stream (see §4) |
Messages use {} placeholders. Name the position, the expected value and the found value —eo::wrong("position {}: expected {}, found {}", i, want, got) — because the message is theonly thing a human sees when a verdict is disputed.
jury_error is not a contestant verdictIt means the problem is broken: a checker bug, an impossible jury answer, a contestant whofound something better than the jury. Never use it for anything a contestant can trigger, andnever return a wrong answer when the jury's answer is impossible — that hides a brokenproblem behind contestant failures.
eo::score(0) is PARTIALLY_CORRECT with 0 points: a well-formed answer whose formula came outat nothing. eo::wrong is WRONG_ANSWER: the output is not a valid answer at all. They pay thesame and read differently, so choose by what happened, not by the points.
The single most important structural rule for a non-trivial checker: write one functionthat reads an answer, verifies it, and returns its value, and run it on both answers.
#include <eolymp.h>
int main(int argc, char** argv) {
eo::checker c(argc, argv);
int n = c.input.read_int(1, 100000, "n");
auto chosen = [n](eo::answer_stream& a) {
int k = a.read_int(0, n, "k");
std::vector<int> items = a.read_ints(k, 1, n, "item");
if (!eo::all_distinct(items)) a.wrong("an item is chosen twice");
return k;
};
c.answers(eo::many);
auto [by_the_jury, found] = c.read_both(chosen);
c.optimum(by_the_jury, found, eo::maximize);
}c.read_both(reader) runs the reader on c.jury first, then on c.output, and returnsboth results. Inside it, a failed read, a.wrong(…) and a bare eo::wrong(…) are all blamedon the stream being read: a jury error during the first call, a wrong answer during the second.So the three rules that matter hold by construction — the jury is read first, the jury's answeris verified too, and a broken jury answer is a jury error rather than every contestant's wronganswer.
c.optimum(by_the_jury, found, eo::minimize) (or eo::maximize) then ends the program:
Outcome | Verdict |
|---|---|
equal | accept |
the contestant is worse |
|
the contestant is better | a jury error: |
For optimisation problems always check both that the answer is feasible and that itsvalue matches — the reader verifies the first, optimum the second.
c.answers(eo::many) declares that several answers are correct. It is a declaration, not acheck: the library then warns (EO212) if the checker only compares with the jury's answer.
YES / NO with a certificate#include <eolymp.h>
int main(int argc, char** argv) {
eo::checker c(argc, argv);
int n = c.input.read_int(1, 100000, "n");
c.yes_no([n](eo::answer_stream& a) {
std::vector<int> p = a.read_ints(n, 1, n, "p");
if (!eo::is_permutation(p)) a.wrong("p is not a permutation");
});
}yes_no compares the first word of each side, ignoring case, checks the certificate after aYES on both sides, and makes a contestant's valid YES against the jury's NO a juryerror. c.yes_no(certificate, "POSSIBLE", "IMPOSSIBLE") takes other words.
c.outputA contestant who prints 2147483647 where a count is expected makes an unbounded checkersize a vector to match, or loop for ever — and a crashing or hanging checker is a systemerror, not a wrong answer. Bound every read from the output by something derived from theinput:
int k = c.output.read_int(0, n, "k"); int at = c.output.read_int(1, n, "position");
A bounded read rejects the value on the spot, as a wrong answer that names it:output.txt, line 1, k: 5000 is above 100. Reading a number from c.output or c.jurywithout bounds compiles with warning EO103.
All three streams are lenient about whitespace — any spaces, tabs and line breaksseparate two values — because a contestant's formatting is not the task. read_line is theone read that looks at lines; at_eof() and at_eoln() look ahead.
Most checkers of such problems never need the jury's answer: "output any valid colouring","any shortest path" — the checker recomputes validity from the input. Write the verifier as areader anyway, so the day the problem gains an optimality criterion you add read_both ratherthan restructure. If only the contestant's answer is read, say so to the library:
c.jury.skip_rest("the answer file holds one valid colouring; any valid one is accepted");Without it, an answer file left unread gets warning EO203 — usually a sign the checker and theanswer files disagree about the format.
eo::score(eo::ratio(good, total), "{} of {} pairs are correct", good, total);eo::score takes a fraction of the test — never points, never a percentage. It readsTEST_COST itself: on a test worth 40, eo::score(0.5) pays 20; on one worth 7 it pays 3.5.Subtask weights stay in the testset configuration, and the checker never hard-codes 100.
eo::ratio(a, b) is exact, so eo::ratio(n, n) is exactly 1 — an accept, not a hairbelow it.
Rounding. When the statement rounds the score, round the same way, or a documentedfull score can be unreachable: eo::score(q, eo::round_to(0), "D = {}", d).
When scoring depends on the subtask, key it off c.group() — the testset index —never off the cost: two subtasks can be worth the same.
The testset decides whether a partial score pays(Design a testset):
A partially scored subtask is scored WORST (the minimum), with the subtask's full valueon every test.
ALL pays nothing for a partial score — only accepted runs count.
A partial score on a test worth 0 is recorded ACCEPTED, because its points equal itscost. So a testset that puts its whole value on the first test and 0 on the rest pays infull for a partially correct solution. Never pair a scoring checker with that layout.
Verify the scoring on the judge, with an attached solution that should earn a partial score:a checker is not proved correct by one subtask paying out.
#include <eolymp.h>
int main(int argc, char** argv) {
eo::checker c(argc, argv);
double expected = c.jury.read_real(eo::any, "area");
double found = c.output.read_real(-1e18, 1e18, "area");
if (!eo::close_enough(expected, found, 1e-6))
eo::wrong("the area is {}, not {}", expected, found);
eo::accept();
}eo::close_enough(expected, found, eps) accepts an absolute or relative error withineps, which is what statements mean; a hand-rolled fabs(a - b) < eps fails at largemagnitudes. For a whole output of numbers, c.reals(1e-6) compares token by token and endsthe program. The output's real numbers may carry an exponent, since many languages print smallvalues that way; c.output.reals(eo::plain) refuses it when the statement fixes the format.
Each gives the verdict and ends the program:
Call | Accepts when |
|---|---|
| exactly the answer's tokens, in order; letter case matters |
| the answer's lines, ignoring trailing spaces and tabs |
| token by token, numbers within |
| §4 |
Use the built-in TOKENS / LINES types when they suffice; these exist for a checker thatalso does something else, and c.tokens() has no 64 KB token limit.
The library writes the verdict line first, whatever the checker printed before it, sostd::cout, printf and eo::log("n = {}", n) cannot break the judge's parse of the score.The log is kept per run.
BLOCKER breaks judging · MAJOR hides defects · MINOR style.
[ ] BLOCKER eo::checker c(argc, argv), #include <eolymp.h> first, runtime cpp:20-gnu14, type PROGRAM
[ ] MAJOR a built-in type (TOKENS / LINES / QUERY_RESULTS) would not have sufficed
[ ] BLOCKER every path ends in a verdict
[ ] BLOCKER composite answer ⇒ one reader run on both answers through c.read_both
[ ] MAJOR trailing output policy deliberate (trailing(eo::ignore) only when the task allows it)
[ ] BLOCKER every read from c.output producing a count, index or length is bounded by the input
[ ] BLOCKER no container sized, and no array indexed, from an unchecked value
[ ] MAJOR every read names its value; no warnings left unexplained (the codes)
[ ] BLOCKER eo::jury_error only for jury-side impossibilities
[ ] BLOCKER a contestant beating the jury is a jury error (c.optimum does this)
[ ] MAJOR feasibility and optimality both checked, for optimisation problems
[ ] MAJOR messages name position, expected and found
[ ] BLOCKER eo::score with a fraction, eo::ratio for "a out of b" — no hard-coded points
[ ] BLOCKER the scored testsets are WORST with the value on every test, not ALL, and not the value on test 1 only
[ ] MAJOR full marks, partial and zero all reachable, and verified on the judge