Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions algorithmic/problems/107/interactor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,13 @@ int main(int argc, char* argv[]) {
if (op == 0) {
i64 y = ouf.readLong();
if (y < 1 || y > 1000000000000000000) {
quitf(_wa, "Invalid query: element %d is out of range [1, 1000000000000000000]", y);
quitf(_wa, "Invalid query: element %lld is out of range [1, 1000000000000000000]", y);
}

println(std::gcd(x, y));
} else if (op == 1) {
int a = ans.readInt(), d = ouf.readInt();
// The guess may be any int, so 2 * d and a - d need 64 bits.
i64 a = ans.readInt(), d = ouf.readInt();
if (std::abs(a - d) <= 7 || (2 * a >= d && 2 * d >= a)) {
max = std::max(max, q);
break;
Expand Down
6 changes: 6 additions & 0 deletions algorithmic/problems/112/chk.cc
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ int main(int argc, char* argv[]) {

double claimed_min_dist = ouf.readDouble();

// testlib reads an overflowing literal such as 1e400 as inf, and the
// relative comparison below would treat inf as equal to any distance.
if (!std::isfinite(claimed_min_dist)) {
quitf(_wa, "Minimum distance is not a finite number");
}

if (claimed_min_dist < 0) {
quitf(_wa, "Minimum distance cannot be negative: %.10f", claimed_min_dist);
}
Expand Down
12 changes: 9 additions & 3 deletions algorithmic/problems/28/interactor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ static double referenceScore(double r) {
#include <vector>
#include <string>
#include <map>
#include <set>

using namespace std;

Expand All @@ -32,6 +33,7 @@ char current[15];
int num[30];
int cnt = 0;
map<string, int>vis;
set<string>dict;

void dfs(int now, int start){
if(cnt >= 1000)return;
Expand Down Expand Up @@ -69,7 +71,9 @@ int main(int argc, char *argv[]) {
int t = inf.readInt(), n = inf.readInt();
for(int i = 0; i < 26; i++){
num[i] = inf.readInt();
for(int j = 1; j <= num[i]; j++)dict.insert(s[i][j]);
}
ensuref((int)dict.size() == n, "The word lists hold %d words, but N is %d", (int)dict.size(), n);
cout<<t<<endl;
cout.flush();
cout<<n<<endl;
Expand All @@ -83,10 +87,12 @@ int main(int argc, char *argv[]) {
int k = ouf.readInt(1, n);
sumk += k;
int start = qs[0]-'a';
// Every word starts with a lowercase letter, so other prefixes match nothing.
int limit = (qs[0] >= 'a' && qs[0] <= 'z') ? num[start] : 0;

int match_cnt = 0;
string ret[1010];
for(int j = 1; j <= num[start]; j++){
for(int j = 1; j <= limit; j++){
int match = 1;
for(int t = 0; t < qs.length(); t++){
if(s[start][j][t] != qs[t])match = 0;
Expand All @@ -105,10 +111,10 @@ int main(int argc, char *argv[]) {
cout.flush();
}
else if(operation == "answer"){
// n distinct dictionary words are the whole dictionary.
for(int j = 1; j <= n; j++){
string answers = ouf.readWord();
int start = answers[0]-'a';
if(answers > s[start][num[start]])quitf(_wa, "Invalid answer");
if(!dict.count(answers))quitf(_wa, "Invalid answer");
if(vis[answers])quitf(_wa, "Repeated answer");
vis[answers] = 1;
}
Expand Down
64 changes: 64 additions & 0 deletions algorithmic/tests/test_problem107.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""A guess must not pass the error check by overflowing 2 * d (#203)."""
import math
from pathlib import Path
import re
import subprocess
import pytest

ROOT = Path(__file__).resolve().parents[2]
DATA = ROOT / 'algorithmic/problems/107/testdata'


@pytest.fixture(scope='module')
def interactor(tmp_path_factory):
executable = tmp_path_factory.mktemp('interactor107') / 'interactor'
subprocess.run(['g++', '-std=gnu++17', '-O2', '-I' + str(ROOT / 'algorithmic/judge/include'),
str(ROOT / 'algorithmic/problems/107/interactor.cpp'), '-o', str(executable)], check=True)
return executable


def play(interactor, tmp_path, data, answer, contestant):
inf, transcript, ans = [tmp_path / name for name in ('in', 'transcript', 'ans')]
inf.write_text(data);ans.write_text(answer)
return subprocess.run([str(interactor), str(inf), str(transcript), str(ans)],
input=contestant, text=True, capture_output=True, timeout=5)


def ratio(result):
return float(re.search(r'Ratio: ([0-9.]+)', result.stderr).group(1))


# 720720 has 240 divisors. In 32-bit arithmetic the first three guesses pass,
# because 2 * d wraps to a positive value.
@pytest.mark.parametrize('guess', [-1500000000, -1073741825, -2147483528, -2147483648, 2147483647])
def test_overflowing_guess_is_wrong(interactor, tmp_path, guess):
result = play(interactor, tmp_path, '1\n720720\n', '240\n', '1 %d\n' % guess)
assert result.returncode == 7, result.stderr
assert 'Wrong guess' in result.stderr and ratio(result) == 0


def test_issue_repro_on_testdata_scores_zero(interactor, tmp_path):
games = int((DATA / '1.in').read_text().split()[0])
result = play(interactor, tmp_path, (DATA / '1.in').read_text(), (DATA / '1.ans').read_text(),
'1 -1500000000\n' * games)
assert result.returncode == 7, result.stderr
assert 'Wrong guess' in result.stderr and ratio(result) == 0


# Accepted iff |guess - divisors| <= 7 or 1/2 <= guess/divisors <= 2.
@pytest.mark.parametrize('x,divisors,guess,correct', [
(720720, 240, 120, True), (720720, 240, 119, False), (720720, 240, 480, True), (720720, 240, 481, False),
(1, 1, -6, True), (1, 1, -7, False), (1, 1, 8, True), (1, 1, 9, False),
])
def test_error_bounds_are_unchanged(interactor, tmp_path, x, divisors, guess, correct):
result = play(interactor, tmp_path, '1\n%d\n' % x, '%d\n' % divisors, '1 %d\n' % guess)
assert result.returncode == 7, result.stderr
assert ('Wrong guess' not in result.stderr) == correct
assert ratio(result) == (1 if correct else 0)


def test_query_count_still_scores(interactor, tmp_path):
result = play(interactor, tmp_path, '1\n720720\n', '240\n', '0 16\n1 240\n')
assert result.returncode == 7, result.stderr
assert result.stdout.split() == ['1', '16']
assert ratio(result) == pytest.approx((60 + math.log(40)) / 100, abs=1e-9)
66 changes: 66 additions & 0 deletions algorithmic/tests/test_problem112.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""A claimed distance must be finite: testlib reads 1e400 as inf (#202)."""
import math
from pathlib import Path
import re
import subprocess
import pytest

ROOT = Path(__file__).resolve().parents[2]
DATA = ROOT / 'algorithmic/problems/112/testdata'
N = int((DATA / '12.in').read_text())


@pytest.fixture(scope='module')
def checker(tmp_path_factory):
executable = tmp_path_factory.mktemp('checker112') / 'checker'
subprocess.run(['g++', '-std=gnu++17', '-O2', '-I' + str(ROOT / 'algorithmic/judge/include'),
str(ROOT / 'algorithmic/problems/112/chk.cc'), '-o', str(executable)], check=True)
return executable


def spread(n):
# Fibonacci lattice on the unit sphere.
points = []
for i in range(n):
z = 1 - (2 * i + 1) / n
theta = i * math.pi * (3 - math.sqrt(5))
points.append((math.sqrt(1 - z * z) * math.cos(theta), math.sqrt(1 - z * z) * math.sin(theta), z))
return points


def output(claim, points):
return claim + '\n' + ''.join('%.17g %.17g %.17g\n' % p for p in points)


def check(checker, tmp_path, text):
out = tmp_path / 'output'
out.write_text(text)
return subprocess.run([str(checker), str(DATA / '12.in'), str(out), str(DATA / '12.ans')],
text=True, capture_output=True, timeout=10)


@pytest.mark.parametrize('claim', ['1e400', '1e309'])
@pytest.mark.parametrize('placement', ['origin', 'spread'])
def test_infinite_claim_is_wrong(checker, tmp_path, claim, placement):
points = [(0.0, 0.0, 0.0)] * N if placement == 'origin' else spread(N)
result = check(checker, tmp_path, output(claim, points))
assert result.returncode == 1, result.stderr
assert 'not a finite number' in result.stderr


def test_infinite_coordinate_is_wrong(checker, tmp_path):
# The sphere check rejects it, since inf * inf is inf; testlib never reads a NaN.
result = check(checker, tmp_path, '0\n1e400 0 0\n' + '0 0 0\n' * (N - 1))
assert result.returncode == 1, result.stderr
assert 'outside the unit sphere' in result.stderr


def test_matching_claim_still_scores(checker, tmp_path):
points = spread(N)
claim = min(math.dist(p, q) for i, p in enumerate(points) for q in points[:i])
result = check(checker, tmp_path, output(repr(claim), points))
assert result.returncode == 7, result.stderr
ref = float((DATA / '12.ans').read_text().split()[0])
lower = 60 * claim / ref
expected = lower if lower <= 60 else min(100, 60 + math.log(claim / ref))
assert float(re.search(r'Ratio: ([0-9.]+)', result.stderr).group(1)) == pytest.approx(expected / 100, abs=1e-9)
85 changes: 85 additions & 0 deletions algorithmic/tests/test_problem28.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""The answer must be the dictionary itself, not any n distinct words (#204)."""
import math
from pathlib import Path
import re
import subprocess
import pytest

ROOT = Path(__file__).resolve().parents[2]
DATA = ROOT / 'algorithmic/problems/28/testdata'


@pytest.fixture(scope='module')
def interactor(tmp_path_factory):
executable = tmp_path_factory.mktemp('interactor28') / 'interactor'
subprocess.run(['g++', '-std=gnu++17', '-O2', '-I' + str(ROOT / 'algorithmic/judge/include'),
str(ROOT / 'algorithmic/problems/28/interactor.cc'), '-o', str(executable)], check=True)
return executable


def words(letter, count):
# The interactor's list for a letter: the first `count` ten-letter words that start with it.
return [letter + 'aaaaaa' + ''.join(chr(97 + j // 26**k % 26) for k in (2, 1, 0)) for j in range(count)]


def play(interactor, tmp_path, data, contestant):
inf, transcript, ans = [tmp_path / name for name in ('in', 'transcript', 'ans')]
inf.write_text(data);ans.write_text('0\n')
return subprocess.run([str(interactor), str(inf), str(transcript), str(ans)],
input=contestant, text=True, capture_output=True, timeout=5)


def ratio(result):
return float(re.search(r'Ratio: ([0-9.]+)', result.stderr).group(1))


# Two words start with 'a' and two with 'c'.
SMALL = '1\n4\n2 0 2' + ' 0' * 23 + '\n'
DICTIONARY = words('a', 2) + words('c', 2)


@pytest.mark.parametrize('answer', [
['a0', 'a1', 'a2', 'a3'],
DICTIONARY[:3] + ['c'],
DICTIONARY[:3] + ['Aaaaaaaaaa'],
DICTIONARY[:3] + ['baaaaaaaaa'],
DICTIONARY[:3] + ['aaaaaaaaac'],
])
def test_word_outside_the_dictionary_is_wrong(interactor, tmp_path, answer):
result = play(interactor, tmp_path, SMALL, 'answer ' + ' '.join(answer) + '\n')
assert result.returncode == 1, result.stderr
assert 'Invalid answer' in result.stderr


def test_repeated_word_is_wrong(interactor, tmp_path):
answer = [DICTIONARY[0]] + DICTIONARY[:3]
result = play(interactor, tmp_path, SMALL, 'answer ' + ' '.join(answer) + '\n')
assert result.returncode == 1, result.stderr
assert 'Repeated answer' in result.stderr


# The first character used to index s[] and num[] unchecked; only '{' to '~'
# landed in the four spare rows.
@pytest.mark.parametrize('prefix', ['A', '0a', '\x7f', '{'])
def test_prefix_without_a_leading_letter_matches_nothing(interactor, tmp_path, prefix):
result = play(interactor, tmp_path, SMALL, 'query %s 1\nanswer %s\n' % (prefix, ' '.join(DICTIONARY)))
assert result.returncode == 7, result.stderr
assert result.stdout.split()[2:] == ['0']
assert ratio(result) == pytest.approx((60 + math.log(2400)) / 100, abs=1e-9)


def test_dictionary_in_any_order_scores(interactor, tmp_path):
result = play(interactor, tmp_path, SMALL, 'query a 2\nquery c 2\nanswer ' + ' '.join(DICTIONARY[::-1]) + '\n')
assert result.returncode == 7, result.stderr
assert result.stdout.split()[2:] == ['2'] + DICTIONARY[:2] + ['2'] + DICTIONARY[2:]
assert ratio(result) == pytest.approx((60 + math.log(2400 / 4)) / 100, abs=1e-9)


@pytest.mark.parametrize('test', range(1, 11))
def test_full_dictionary_of_each_test_is_accepted(interactor, tmp_path, test):
data = (DATA / ('%d.in' % test)).read_text()
counts = list(map(int, data.split()[2:28]))
answer = [w for i, count in enumerate(counts) for w in words(chr(97 + i), count)]
result = play(interactor, tmp_path, data, 'answer ' + ' '.join(answer) + '\n')
assert result.returncode == 7, result.stderr
assert ratio(result) == 1
Loading