diff problem.py @ 76:0e5ae28e0b2b

Points are now weighted on a test context basis In particular, this has allowed for simple extensions to the format of testconf to award points to whole test groups without at the same time compromising the future ability of giving partial score for correct but slow solutions. Specifically, the groupweight configuration variable has been added and normally has the format {groupindex: points} where groupindex is the group's index in the tests configuration variable. The backwards incompatible change is that test contexts are no longer guaranteed to learn the score awarded or the maximum possible score for every test case and may instead be notified about them in batches. In other news, the pointmap and groupweight configuration variables can (now) be given as sequences in addition to mappings. (Technically, the distinction currently made is dict versus everything else.) Items of a sequence pointmap/groupweight correspond directly to the test cases/ groups defined in the tests configuration variable; in particular, when groups are used, tests=[1],[2,3];pointmap={1:1,2:2,3:3} can now be written as pointmap=tests=[1],[2,3]. Missing items are handled in the same way in which they are handled when the variable is a mapping. Note that the items of groupweight correspond to whole test groups rather than individual test cases. In other news again, the wording of problem total lines has been changed from '<unweighted> points; weighted score: <weighted>' to '<weighted> points (<unweighted> before weighting)', and group total lines now properly report fractional numbers of points (this is a bug fix).
author Oleg Oshmyan <chortos@inbox.lv>
date Sat, 08 Jan 2011 16:03:35 +0200
parents 007f7eb6fb2b
children 69eadc60f4e2
line wrap: on
line diff
--- a/problem.py	Thu Jan 06 23:53:31 2011 +0200
+++ b/problem.py	Sat Jan 08 16:03:35 2011 +0200
@@ -52,9 +52,10 @@
 test_context_end = object()
 
 class TestGroup(TestContext):
-	__slots__ = 'case', 'log', 'correct', 'allcorrect', 'real', 'max', 'ntotal', 'nvalued', 'ncorrect', 'ncorrectvalued'
+	__slots__ = 'points', 'case', 'log', 'correct', 'allcorrect', 'real', 'max', 'ntotal', 'nvalued', 'ncorrect', 'ncorrectvalued'
 	
-	def __init__(self):
+	def __init__(self, points=None):
+		self.points = points
 		self.real = self.max = self.ntotal = self.nvalued = self.ncorrect = self.ncorrectvalued = 0
 		self.allcorrect = True
 		self.log = []
@@ -63,7 +64,6 @@
 		self.case = case
 		self.correct = False
 		self.ntotal += 1
-		self.max += case.points
 		if case.points:
 			self.nvalued += 1
 	
@@ -73,21 +73,29 @@
 		if self.case.points:
 			self.ncorrectvalued += 1
 	
-	def case_end(self, granted):
-		self.log.append((self.case, self.correct, granted))
-		self.real += granted
+	def case_end(self):
+		self.log.append((self.case, self.correct))
 		del self.case
 		if not self.correct:
 			self.allcorrect = False
 	
+	def score(self, real, max):
+		self.real += real
+		self.max += max
+	
 	def end(self):
-		say('Group total: %d/%d tests; %d/%d points' % (self.ncorrect, self.ntotal, self.real if self.allcorrect else 0, self.max))
+		if not self.allcorrect:
+			self.real = 0
+		if self.points is not None and self.points != self.max:
+			max, weighted = self.points, self.real * self.points / self.max if self.max else 0
+			before_weighting = ' (%g/%g before weighting)' % (self.real, self.max)
+		else:
+			max, weighted = self.max, self.real
+			before_weighting = ''
+		say('Group total: %d/%d tests, %g/%g points%s' % (self.ncorrect, self.ntotal, weighted, max, before_weighting))
 		# No real need to flush stdout, as it will anyway be flushed in a moment,
 		# when either the problem total or the next test case's ID is printed
-		if self.allcorrect:
-			return self.log
-		else:
-			return ((case, correct, 0) for case, correct, granted in self.log)
+		return weighted, max, self.log
 
 class Problem(object):
 	__slots__ = 'name', 'config', 'cache', 'testcases'
@@ -111,11 +119,13 @@
 			contexts = deque((TestGroup(),))
 			for case in prob.testcases:
 				if case is test_context_end:
-					for case, correct, granted in contexts.pop().end():
+					real, max, log = contexts.pop().end()
+					for case, correct in log:
 						contexts[-1].case_start(case)
 						if correct:
 							contexts[-1].case_correct()
-						contexts[-1].case_end(granted)
+						contexts[-1].case_end()
+					contexts[-1].score(real, max)
 					continue
 				elif isinstance(case, TestContext):
 					contexts.append(case)
@@ -181,12 +191,15 @@
 						verdict = 'partly correct' + comment
 					granted *= case.points
 				say('%g/%g, %s' % (granted, case.points, verdict))
-				contexts[-1].case_end(granted)
+				contexts[-1].case_end()
+				contexts[-1].score(granted, case.points)
 			weighted = contexts[0].real * prob.config.taskweight / contexts[0].max if contexts[0].max else 0
+			before_weighting = valued = ''
+			if prob.config.taskweight != contexts[0].max:
+				before_weighting = ' (%g/%g before weighting)' % (contexts[0].real, contexts[0].max)
 			if contexts[0].nvalued != contexts[0].ntotal:
-				say('Problem total: %d/%d tests (%d/%d valued); %g/%g points; weighted score: %g/%g' % (contexts[0].ncorrect, contexts[0].ntotal, contexts[0].ncorrectvalued, contexts[0].nvalued, contexts[0].real, contexts[0].max, weighted, prob.config.taskweight))
-			else:
-				say('Problem total: %d/%d tests; %g/%g points; weighted score: %g/%g' % (contexts[0].ncorrect, contexts[0].ntotal, contexts[0].real, contexts[0].max, weighted, prob.config.taskweight))
+				valued = ' (%d/%d valued)' % (contexts[0].ncorrectvalued, contexts[0].nvalued)
+			say('Problem total: %d/%d tests%s, %g/%g points%s' % (contexts[0].ncorrect, contexts[0].ntotal, valued, weighted, prob.config.taskweight, before_weighting))
 			sys.stdout.flush()
 			return weighted, prob.config.taskweight
 		finally: