-
Notifications
You must be signed in to change notification settings - Fork 402
Allow configuring subtasks to always show testcases. #1677
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,7 @@ | |
|
|
||
| import logging | ||
| import re | ||
| from typing import TypedDict, NotRequired | ||
| from abc import ABCMeta, abstractmethod | ||
|
|
||
| from cms import FEEDBACK_LEVEL_RESTRICTED | ||
|
|
@@ -192,26 +193,38 @@ class ScoreTypeAlone(ScoreType): | |
| pass | ||
|
|
||
|
|
||
| class ScoreTypeGroupParametersDict(TypedDict): | ||
| max_score: float | ||
| testcases: int | str | list[str] | ||
| threshold: NotRequired[float] | ||
| always_show_testcases: NotRequired[bool] | ||
|
|
||
|
|
||
| # the format of parameters is impossible to type-hint correctly, it seems... | ||
| # this hint is (mostly) correct for the methods this base class implements, | ||
| # subclasses might need a longer tuple. | ||
| ScoreTypeGroupParameters = tuple[float, int | str | list[str]] | ScoreTypeGroupParametersDict | ||
|
|
||
|
|
||
| class ScoreTypeGroup(ScoreTypeAlone): | ||
| """Intermediate class to manage tasks whose testcases are | ||
| subdivided in groups (or subtasks). The score type parameters must | ||
| be in the form [[m, t, ...], [...], ...], where m is the maximum | ||
| score for the given subtask and t is the parameter for specifying | ||
| testcases. | ||
| testcases, or be a list of dicts matching ScoreTypeGroupParametersDict. | ||
|
|
||
| If t is int, it is interpreted as the number of testcases | ||
| comprising the subtask (that are consumed from the first to the | ||
| last, sorted by num). If t is unicode, it is interpreted as the regular | ||
| expression of the names of target testcases. All t must have the same type. | ||
| expression of the names of target testcases. If t is a list of strings, | ||
| it is interpreted as a list of testcase codenames. All t must have the | ||
| same type. | ||
|
|
||
| A subclass must implement the method 'get_public_outcome' and | ||
| 'reduce'. | ||
|
|
||
| """ | ||
| # the format of parameters is impossible to type-hint correctly, it seems... | ||
| # this hint is (mostly) correct for the methods this base class implements, | ||
| # subclasses might need a longer tuple. | ||
| parameters: list[tuple[float, int | str]] | ||
| parameters: list[ScoreTypeGroupParameters] | ||
|
|
||
| # Mark strings for localization. | ||
| N_("Subtask %(index)s") | ||
|
|
@@ -333,6 +346,24 @@ class ScoreTypeGroup(ScoreTypeAlone): | |
| </div> | ||
| {% endfor %}""" | ||
|
|
||
| def get_max_score(self, group_parameter: ScoreTypeGroupParameters) -> float: | ||
| if isinstance(group_parameter, tuple) or isinstance(group_parameter, list): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can these ever legitemately be tuples? these parameters should be loaded from json, which doesn't return tuples. the type hint says tuple only because there is no way to type-hint a list whose elements have different types 🙃 (actually, that should probably be added to the comment near the definition of ScoreTypeGroupParameters) if you assumed doing an isinstance(tuple) here would get the type checker to behave on the next line, then unfortunately that doesn't appear to be the case, as pyright at least assumes that the parameter is some kind of magical intersection of list and ScoreTypeGroupParametersDict. |
||
| return group_parameter[0] | ||
| else: | ||
| return group_parameter["max_score"] | ||
|
|
||
| def get_testcases(self, group_parameter: ScoreTypeGroupParameters) -> int | str | list[str]: | ||
| if isinstance(group_parameter, tuple) or isinstance(group_parameter, list): | ||
| return group_parameter[1] | ||
| else: | ||
| return group_parameter["testcases"] | ||
|
|
||
| def get_always_show_testcases(self, group_parameter: ScoreTypeGroupParameters) -> bool: | ||
| if isinstance(group_parameter, tuple) or isinstance(group_parameter, list): | ||
| return False | ||
| else: | ||
| return group_parameter.get("always_show_testcases", False) | ||
|
|
||
| def retrieve_target_testcases(self) -> list[list[str]]: | ||
| """Return the list of the target testcases for each subtask. | ||
|
|
||
|
|
@@ -345,7 +376,7 @@ def retrieve_target_testcases(self) -> list[list[str]]: | |
|
|
||
| """ | ||
|
|
||
| t_params = [p[1] for p in self.parameters] | ||
| t_params = [self.get_testcases(p) for p in self.parameters] | ||
|
|
||
| if all(isinstance(t, int) for t in t_params): | ||
|
|
||
|
|
@@ -379,9 +410,12 @@ def retrieve_target_testcases(self) -> list[list[str]]: | |
|
|
||
| return targets | ||
|
|
||
| elif all(isinstance(t, list) for t in t_params) and all(all(isinstance(t, str) for t in s) for s in t_params): | ||
| return t_params | ||
|
|
||
| raise ValueError( | ||
| "In the score type parameters, the second value of each element " | ||
| "must have the same type (int or unicode)") | ||
| "must have the same type (int, unicode or list of strings)") | ||
|
|
||
| def max_scores(self): | ||
| """See ScoreType.max_score.""" | ||
|
|
@@ -393,10 +427,10 @@ def max_scores(self): | |
|
|
||
| for st_idx, parameter in enumerate(self.parameters): | ||
| target = targets[st_idx] | ||
| score += parameter[0] | ||
| score += self.get_max_score(parameter) | ||
| if all(self.public_testcases[tc_idx] for tc_idx in target): | ||
| public_score += parameter[0] | ||
| headers += ["Subtask %d (%g)" % (st_idx, parameter[0])] | ||
| public_score += self.get_max_score(parameter) | ||
| headers += ["Subtask %d (%g)" % (st_idx, self.get_max_score(parameter))] | ||
|
|
||
| return score, public_score, headers | ||
|
|
||
|
|
@@ -461,10 +495,10 @@ def compute_score(self, submission_result): | |
| st_score_fraction = self.reduce( | ||
| [float(evaluations[tc_idx].outcome) for tc_idx in target], | ||
| parameter) | ||
| st_score = st_score_fraction * parameter[0] | ||
| st_score = st_score_fraction * self.get_max_score(parameter) | ||
| rounded_score = round(st_score, score_precision) | ||
|
|
||
| if tc_first_lowest_idx is not None and st_score_fraction < 1.0: | ||
| if tc_first_lowest_idx is not None and st_score_fraction < 1.0 and not self.get_always_show_testcases(parameter): | ||
| for tc in testcases: | ||
| if not self.public_testcases[tc["idx"]]: | ||
| continue | ||
|
|
@@ -482,7 +516,7 @@ def compute_score(self, submission_result): | |
| "score_fraction": st_score_fraction, | ||
| # But we also want the properly rounded score for display. | ||
| "score": rounded_score, | ||
| "max_score": parameter[0], | ||
| "max_score": self.get_max_score(parameter), | ||
| "testcases": testcases}) | ||
| if all(self.public_testcases[tc_idx] for tc_idx in target): | ||
| public_score += st_score | ||
|
|
@@ -495,7 +529,7 @@ def compute_score(self, submission_result): | |
| return score, subtasks, public_score, public_subtasks, ranking_details | ||
|
|
||
| @abstractmethod | ||
| def get_public_outcome(self, outcome: float, parameter: list) -> str: | ||
| def get_public_outcome(self, outcome: float, parameter: ScoreTypeGroupParameters) -> str: | ||
| """Return a public outcome from an outcome. | ||
|
|
||
| The public outcome is shown to the user, and this method | ||
|
|
@@ -512,7 +546,7 @@ def get_public_outcome(self, outcome: float, parameter: list) -> str: | |
| pass | ||
|
|
||
| @abstractmethod | ||
| def reduce(self, outcomes: list[float], parameter: list) -> float: | ||
| def reduce(self, outcomes: list[float], parameter: ScoreTypeGroupParameters) -> float: | ||
| """Return the score of a subtask given the outcomes. | ||
|
|
||
| outcomes: the outcomes of the submission in | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm a little unhappy about this scoretype-specific field being here, but well, the typing of the parameters is a huge mess anyways and this is hardly the worst part of it.
i don't think it's possible to fix it without refactoring how scoretypes work in general. python doesn't have associated types, so i don't think there is even a good way to express "this scoretype uses this subtype of the parameters dict for its methods". so we can just continue ignoring the type errors.