Name | Total Lines | Lines of Code | Total Coverage | Code Coverage |
---|---|---|---|---|
lib/jldrill/model/Quiz/Strategy.rb | 330 | 232 | 90.00%
|
85.78%
|
Code reported as executed by Ruby looks like this...and this: this line is also marked as covered.Lines considered as run by rcov, but not reported by Ruby, look like this,and this: these lines were inferred by rcov (using simple heuristics).Finally, here's a line marked as not executed.
1 require 'jldrill/model/Quiz/Statistics' |
2 require 'jldrill/model/problems/ProblemFactory' |
3 |
4 module JLDrill |
5 |
6 # Strategy for choosing, promoting and demoting items in |
7 # the quiz. |
8 class Strategy |
9 attr_reader :stats |
10 |
11 def initialize(quiz) |
12 @quiz = quiz |
13 @stats = Statistics.new(quiz) |
14 end |
15 |
16 def Strategy.newSetBin |
17 return 0 |
18 end |
19 |
20 def Strategy.workingSetBins |
21 return 1..3 |
22 end |
23 |
24 def Strategy.reviewSetBin |
25 return 4 |
26 end |
27 |
28 def Strategy.forgottenSetBin |
29 return 5 |
30 end |
31 |
32 # Returns a string showing the status of the quiz with this strategy |
33 def status |
34 if shouldReview? |
35 retVal = " #{@stats.recentAccuracy}%" |
36 if @stats.inTargetZone? |
37 retVal += " - #{(10 - @stats.timesInTargetZone)}" |
38 end |
39 elsif !forgottenSet.empty? |
40 retVal = " Forgotten Items" |
41 else |
42 retVal = " New Items" |
43 end |
44 retVal |
45 end |
46 |
47 # Returns the contents for the quiz |
48 def contents |
49 @quiz.contents |
50 end |
51 |
52 # Returns the options for the quiz |
53 def options |
54 @quiz.options |
55 end |
56 |
57 def newSet |
58 @quiz.contents.bins[Strategy.newSetBin] |
59 end |
60 |
61 def workingSetEmpty? |
62 contents.rangeEmpty?(Strategy.workingSetBins) |
63 end |
64 |
65 # Returns the number of items in the working set |
66 def workingSetSize |
67 contents.bins[1].length + contents.bins[2].length + contents.bins[3].length |
68 end |
69 |
70 # Returns true if the working set is not full |
71 def workingSetFull? |
72 workingSetSize >= options.introThresh |
73 end |
74 |
75 def reviewSet |
76 @quiz.contents.bins[Strategy.reviewSetBin] |
77 end |
78 |
79 # Returns the number of items in the review set |
80 def reviewSetSize |
81 reviewSet.length |
82 end |
83 |
84 def forgottenSet |
85 @quiz.contents.bins[Strategy.forgottenSetBin] |
86 end |
87 |
88 def forgottenSetSize |
89 forgottenSet.length |
90 end |
91 |
92 # Sort the items according to their schedule |
93 def reschedule |
94 # Check for legacy files that may have Kanji |
95 # problems schedules but no kanji |
96 reviewSet.each do |x| |
97 x.removeInvalidKanjiProblems |
98 end |
99 # Sort the review set |
100 reviewSet.sort! do |x,y| |
101 x.schedule.reviewLoad <=> y.schedule.reviewLoad |
102 end |
103 # Move old items to the forgotten set |
104 while ((options.forgettingThresh != 0.0) && |
105 (!reviewSet.empty?) && |
106 (reviewSet[0].schedule.reviewRate >= options.forgettingThresh.to_f)) |
107 contents.moveToBin(reviewSet[0], Strategy.forgottenSetBin) |
108 end |
109 # Sort the forgotten set |
110 forgottenSet.sort! do |x,y| |
111 x.schedule.reviewLoad <=> y.schedule.reviewLoad |
112 end |
113 # If the user changes the settings then we may have to |
114 # unforget some items |
115 while ((!forgottenSet.empty?) && |
116 ((options.forgettingThresh == 0.0) || |
117 (forgottenSet.last.schedule.reviewRate < |
118 options.forgettingThresh.to_f))) |
119 contents.moveToBin(forgottenSet.last, Strategy.reviewSetBin) |
120 end |
121 # Sort the review set again |
122 reviewSet.sort! do |x,y| |
123 x.schedule.reviewLoad <=> y.schedule.reviewLoad |
124 end |
125 |
126 end |
127 |
128 # Returns true if the review set has been |
129 # reviewed enough that it is considered to be |
130 # known. This happens when we have reviewed |
131 # ten items while in the target zone. |
132 # The target zone occurs when in the last 10 |
133 # items, we have a 90% success rate or when |
134 # we have a 90% confidence that the the items |
135 # have a 90% chance of success. |
136 def reviewSetKnown? |
137 !(10 - @stats.timesInTargetZone > 0) |
138 end |
139 |
140 # Returns true if at least one working set full of |
141 # items have been promoted to the review set, and |
142 # the review set is not known to the required |
143 # level. |
144 # Note: if the new set and the working set are |
145 # both empty, this will always return true. |
146 def shouldReview? |
147 # if we only have review set items, or we are in review mode |
148 # then return true |
149 if (newSet.empty? && workingSetEmpty?) || (options.reviewMode) |
150 return true |
151 end |
152 |
153 !reviewSetKnown? && (reviewSetSize >= options.introThresh) && |
154 !(reviewSet.allSeen?) |
155 end |
156 |
157 # Return the index of the first item in the bin that hasn't been |
158 # seen yet. If they have all been seen, reset the bin |
159 def findUnseen(binNum) |
160 bin = contents.bins[binNum] |
161 if bin.empty? |
162 return -1 |
163 end |
164 |
165 if bin.allSeen? |
166 bin.setUnseen |
167 end |
168 bin.firstUnseen |
169 end |
170 |
171 # Returns a random unseen item. Return nil if the range is empty. |
172 # Resets the seen status if all the items are already seen. |
173 def randomUnseen(range) |
174 if contents.rangeEmpty?(range) |
175 return nil |
176 end |
177 if contents.rangeAllSeen?(range) |
178 range.to_a.each do |bin| |
179 contents.bins[bin].setUnseen |
180 end |
181 end |
182 index = rand(contents.numUnseen(range)) |
183 item = contents.findUnseen(index, range) |
184 item |
185 end |
186 |
187 # Get an item from the New Set |
188 def getNewItem |
189 if options.randomOrder |
190 index = rand(newSet.length) |
191 else |
192 index = findUnseen(Strategy.newSetBin) |
193 end |
194 if !(index == -1) |
195 item = newSet[index] |
196 # Resetting the schedule to make up for the consequences |
197 # of an old bug where reset drills weren't reset properly. |
198 item.resetSchedules |
199 promote(item) |
200 item |
201 else |
202 nil |
203 end |
204 end |
205 |
206 # Get an item from the Review Set |
207 def getReviewItem |
208 reviewSet[0] |
209 end |
210 |
211 def getForgottenItem |
212 forgottenSet[0] |
213 end |
214 |
215 # Get an item from the Working Set |
216 def getWorkingItem |
217 randomUnseen(Strategy.workingSetBins) |
218 end |
219 |
220 # Get an item to quiz |
221 def getItem |
222 item = nil |
223 if contents.empty? |
224 return nil |
225 end |
226 |
227 if !workingSetFull? |
228 if shouldReview? |
229 item = getReviewItem |
230 elsif !forgottenSet.empty? |
231 item = getForgottenItem |
232 elsif !newSet.empty? |
233 item = getNewItem |
234 end |
235 end |
236 |
237 # Usually we get a working item if the above is not true |
238 item = getWorkingItem if item.nil? |
239 |
240 item.allSeen(true) |
241 return item |
242 end |
243 |
244 # Create a problem for the given item at the correct level |
245 def createProblem(item) |
246 item.itemStats.createProblem |
247 @stats.startTimer(item.bin == Strategy.reviewSetBin) |
248 # Drill at the scheduled level in the review and forgotten sets |
249 if (item.bin == Strategy.reviewSetBin) || |
250 (item.bin == Strategy.forgottenSetBin) |
251 problem = item.problem |
252 else |
253 # Otherwise drill for the specific bin |
254 level = item.bin - 1 |
255 problem = ProblemFactory.create(level, item) |
256 end |
257 return problem |
258 end |
259 |
260 # Promote the item to the next level/bin |
261 def promote(item) |
262 if !item.nil? |
263 item.setScores(0) |
264 if item.bin < 3 |
265 item.setLevels(item.bin) |
266 contents.moveToBin(item, item.bin + 1) |
267 else |
268 if item.bin == 3 |
269 # Newly promoted items |
270 item.itemStats.consecutive = 1 |
271 @stats.learned += 1 |
272 item.scheduleAll |
273 end |
274 # Put the item at the back of the bin |
275 contents.bins[item.bin].delete(item) |
276 reviewSet.push(item) |
277 end |
278 end |
279 end |
280 |
281 # Demote the item |
282 def demote(item) |
283 if !item.nil? |
284 item.demoteAll |
285 if (item.bin != 0) |
286 contents.moveToBin(item, 1) |
287 else |
288 # Demoting bin 0 items is non-sensical, but it should do |
289 # something sensible anyway. |
290 contents.moveToBin(item, 0) |
291 end |
292 end |
293 end |
294 |
295 # Mark the item as having been reviewed correctly |
296 def correct(item) |
297 @stats.correct(item) |
298 item.itemStats.correct |
299 if ((item.bin == Strategy.reviewSetBin) || |
300 (item.bin == Strategy.forgottenSetBin)) |
301 item.schedule.correct |
302 promote(item) |
303 else |
304 item.allCorrect |
305 if(item.schedule.score >= options.promoteThresh) |
306 promote(item) |
307 end |
308 end |
309 end |
310 |
311 # Mark the item as having been reviewed incorrectly |
312 def incorrect(item) |
313 @stats.incorrect(item) |
314 item.allIncorrect |
315 item.itemStats.incorrect |
316 demote(item) |
317 end |
318 |
319 # Promote the item from the working set into the review |
320 # set without any further training. If it is already |
321 # in the review set, simply mark it correct. |
322 def learn(item) |
323 if item.bin <= 3 |
324 item.setScores(options.promoteThresh) |
325 contents.moveToBin(item, 3) |
326 end |
327 correct(item) |
328 end |
329 end |
330 end |
Generated on Mon May 23 16:17:46 +0900 2011 with rcov 0.9.8