Skip to content

Commit

Permalink
New "explanation" field has been added to the problem test-cases sett…
Browse files Browse the repository at this point in the history
…ings, the data included in the 'xpl' file will be displayed at the problem statement after its related output.

The test-case information on the problem statement has been arranged by test-cases in order to make it more readable.
  • Loading branch information
FherStk committed Oct 1, 2024
1 parent 8fec955 commit 50d0d70
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 18 deletions.
18 changes: 18 additions & 0 deletions judge/migrations/0148_test_case_explanation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.25 on 2024-08-18 07:16

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('judge', '0147_infer_test_cases_from_zip'),
]

operations = [
migrations.AddField(
model_name='problemtestcase',
name='explanation_file',
field=models.CharField(blank=True, null=True, default='', max_length=100, verbose_name='explanation file name'),
),
]
53 changes: 37 additions & 16 deletions judge/models/problem_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,40 +104,40 @@ def setup_test_cases_content(self):

for i, tc in enumerate([x for x in test_cases if x.is_pretest]):
self.append_tescase_to_statement(zip, content, tc, i)
last = i

if last > 0:
last += 1
last = i + 1

for i, tc in enumerate([x for x in test_cases if not x.is_pretest]):
self.append_tescase_to_statement(zip, content, tc, i + last)

self.test_cases_content = '\n'.join(content)

def append_tescase_to_statement(self, zip, content, tc, i):
content.append(f'## Sample Input {i+1}')
content.append('')
content.append(f'## Test Case {i+1}')

if tc.is_private:
content.append('*Hidden: this is a private test case!* ')

else:
content.append('### Input')
content.append('```')
content.append(zip.read(tc.input_file).decode('utf-8'))
if tc.input_file != '':
content.append(zip.read(tc.input_file).decode('utf-8'))
content.append('```')

content.append('')
content.append(f'## Sample Output {i+1}')
content.append('')

if tc.is_private:
content.append('*Hidden: this is a private test case!* ')

else:
content.append('')
content.append('### Output')
content.append('```')
content.append(zip.read(tc.output_file).decode('utf-8'))
if tc.output_file != '':
content.append(zip.read(tc.output_file).decode('utf-8'))
content.append('```')

if tc.explanation_file != '':
content.append('')
content.append('### Explanation')

if tc.explanation_file != '':
content.append(zip.read(tc.explanation_file).decode('utf-8'))

content.append('')

def infer_test_cases_from_zip(self):
Expand All @@ -157,6 +157,22 @@ def infer_test_cases_from_zip(self):
input = [x for x in files if '.in' in x or ('input' in x and '.' in x)]
output = [x for x in files if '.out' in x or ('output' in x and '.' in x)]

# Not all explanations are mandatory, so there can be gaps!
input.sort()
output.sort()
explanation = []
for i in range(len(input)):
in_file = input[i]

xpl_file = in_file.replace('input', 'explanation') if 'input' in in_file else in_file.replace('in', 'xpl')
found = [x for x in files if xpl_file in x]
found.sort()

if len(found) > 0:
explanation.append(found[0])
else:
explanation.append('')

cases = []
for i in range(len(input)):
list = ProblemTestCase.objects.filter(dataset_id=self.problem.pk, input_file=input[i],
Expand All @@ -173,6 +189,7 @@ def infer_test_cases_from_zip(self):
ptc.order = i
ptc.input_file = input[i]
ptc.output_file = output[i]
ptc.explanation_file = explanation[i]
ptc.points = 0

cases.append(ptc)
Expand Down Expand Up @@ -242,6 +259,9 @@ def _load_test_case_from_doc(self, doc, field, is_pretest):
if test.get('out'):
ptc.output_file = test['out']

if test.get('xpl'):
ptc.explanation_file = test['xpl']

if test.get('points'):
ptc.points = test['points']
else:
Expand Down Expand Up @@ -287,6 +307,7 @@ class ProblemTestCase(models.Model):
default='C')
input_file = models.CharField(max_length=100, verbose_name=_('input file name'), blank=True)
output_file = models.CharField(max_length=100, verbose_name=_('output file name'), blank=True)
explanation_file = models.CharField(max_length=100, verbose_name=_('explanation file name'), blank=True)
generator_args = models.TextField(verbose_name=_('generator arguments'), blank=True)
points = models.IntegerField(verbose_name=_('point value'), blank=True, null=True)
is_pretest = models.BooleanField(verbose_name=_('case is pretest?'), default=False)
Expand Down
7 changes: 7 additions & 0 deletions judge/utils/problem_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,16 @@ def make_checker(case):
if case.output_file not in self.files:
raise ProblemDataError(_('Output file for case %d does not exist: %s') %
(i, case.output_file))
if case.explanation_file != '' and case.explanation_file not in self.files:
raise ProblemDataError(_('Explanation file for case %d does not exist: %s') %
(i, case.explanation_file))

if case.input_file:
data['in'] = case.input_file
if case.output_file:
data['out'] = case.output_file
if case.explanation_file:
data['xpl'] = case.explanation_file
if case.points is not None:
data['points'] = case.points
if case.generator_args:
Expand Down Expand Up @@ -151,6 +156,7 @@ def make_checker(case):
case.checker_args = ''
case.input_file = ''
case.output_file = ''
case.explanation_file = ''
case.save(update_fields=('checker_args', 'input_file', 'output_file'))
elif case.type == 'E':
if not batch:
Expand All @@ -159,6 +165,7 @@ def make_checker(case):
case.is_private = batch['is_private']
case.input_file = ''
case.output_file = ''
case.explanation_file = ''
case.generator_args = ''
case.checker = ''
case.checker_args = ''
Expand Down
5 changes: 3 additions & 2 deletions judge/views/problem_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ class ProblemCaseForm(ModelForm):

class Meta:
model = ProblemTestCase
fields = ('order', 'type', 'input_file', 'output_file', 'points', 'is_pretest', 'is_private', 'output_limit',
'output_prefix', 'checker', 'checker_args', 'generator_args', 'batch_dependencies')
fields = ('order', 'type', 'input_file', 'output_file', 'explanation_file', 'points', 'is_pretest',
'is_private', 'output_limit', 'output_prefix', 'checker', 'checker_args', 'generator_args',
'batch_dependencies')
widgets = {
'generator_args': HiddenInput,
'batch_dependencies': HiddenInput,
Expand Down
5 changes: 5 additions & 0 deletions templates/problem/data.html
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@
<th class="type-column">{{ _('Type') }}</th>
<th>{{ _('Input file') }}</th>
<th>{{ _('Output file') }}</th>
<th>{{ _('Explanation file') }}</th>
<th>{{ _('Points') }}</th>
<th>{{ _('Pretest?') }}</th>
<th>{{ _('Private?') }}</th>
Expand Down Expand Up @@ -439,6 +440,10 @@
form['output_file'].value() in valid_files) %} class="bad-file"{% endif %}>
{{ form.output_file.errors }}{{ form.output_file }}
</td>
<td{% if not (form.empty_permitted or form['type'].value() != 'C' or form['explanation_file'].value() == '' or
form['explanation_file'].value() in valid_files) %} class="bad-file"{% endif %}>
{{ form.explanation_file.errors }}{{ form.explanation_file }}
</td>
<td>{{ form.points.errors }}{{ form.points }}</td>
<td>{{ form.is_pretest.errors }}{{ form.is_pretest }}</td>
<td>{{ form.is_private.errors }}{{ form.is_private }}</td>
Expand Down

0 comments on commit 50d0d70

Please sign in to comment.