From 8308c79fa6276c8fe15a0f497fccd0d44fb48bc7 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 5 Dec 2011 19:15:46 +0900 Subject: [PATCH] minor refactor of count handling - drop lrnCount; rename lrnRepCount to lrnCount - on card fetch, decr count by card.left - drop cardCounts(), rename repCounts() to just counts() - fix lrn count bugs --- anki/sched.py | 66 ++++++++++++--------------- anki/sync.py | 2 +- tests/test_sched.py | 103 ++++++++++++++++-------------------------- tests/test_undo.py | 14 +++--- tests/test_upgrade.py | 2 +- 5 files changed, 77 insertions(+), 110 deletions(-) diff --git a/anki/sched.py b/anki/sched.py index 5cfb4adb3..8a8490f90 100644 --- a/anki/sched.py +++ b/anki/sched.py @@ -29,11 +29,10 @@ class Scheduler(object): def getCard(self): "Pop the next card from the queue. None if finished." self._checkDay() - id = self._getCardId() - if id: - c = self.col.getCard(id) - c.startTimer() - return c + card = self._getCard() + if card: + card.startTimer() + return card def reset(self): self._updateCutoff() @@ -52,7 +51,6 @@ class Scheduler(object): card.queue = 1 card.type = 1 card.left = self._startingLeft(card) - self.lrnRepCount += card.left self._updateStats(card, 'new') if card.queue == 1: self._answerLrnCard(card, ease) @@ -68,10 +66,7 @@ class Scheduler(object): card.usn = self.col.usn() card.flushSched() - def repCounts(self): - return (self.newCount, self.lrnRepCount, self.revCount) - - def cardCounts(self): + def counts(self): return (self.newCount, self.lrnCount, self.revCount) def dueForecast(self, days=7): @@ -175,23 +170,23 @@ order by due""" % self._deckLimit(), # Getting the next card ########################################################################## - def _getCardId(self): + def _getCard(self): "Return the next due card id, or None." # learning card due? - id = self._getLrnCard() - if id: - return id + c = self._getLrnCard() + if c: + return c # new first, or time for one? if self._timeForNewCard(): return self._getNewCard() # card due for review? - id = self._getRevCard() - if id: - return id + c = self._getRevCard() + if c: + return c # new cards left? - id = self._getNewCard() - if id: - return id + c = self._getNewCard() + if c: + return c # collapse or finish return self._getLrnCard(collapse=True) @@ -266,7 +261,7 @@ select id, due from cards where did = ? and queue = 0 limit ?""", did, lim) # we only have one note in the queue; stop rotating break self.newCount -= 1 - return id + return self.col.getCard(id) def _updateNewCardRatio(self): if self.col.decks.top()['newSpread'] == NEW_CARDS_DISTRIBUTE: @@ -316,12 +311,11 @@ select id, due from cards where did = ? and queue = 0 limit ?""", did, lim) ########################################################################## def _resetLrnCount(self): - (self.lrnCount, self.lrnRepCount) = self.col.db.first(""" -select count(), sum(left) from (select left from cards where + self.lrnCount = self.col.db.scalar(""" +select sum(left) from (select left from cards where did in %s and queue = 1 and due < ? limit %d)""" % ( self._deckLimit(), self.reportLimit), - self.dayCutoff) - self.lrnRepCount = self.lrnRepCount or 0 + self.dayCutoff) or 0 def _resetLrn(self): self._resetLrnCount() @@ -347,9 +341,9 @@ limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff) cutoff += self.col.decks.top()['collapseTime'] if self._lrnQueue[0][0] < cutoff: id = heappop(self._lrnQueue)[1] - self.lrnCount -= 1 - self.lrnRepCount -= 1 - return id + card = self.col.getCard(id) + self.lrnCount -= card.left + return card def _answerLrnCard(self, card, ease): # ease 1=no, 2=yes, 3=remove @@ -359,29 +353,30 @@ limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff) else: type = 0 leaving = False + # lrnCount was decremented once when card was fetched lastLeft = card.left + # immediate graduate? if ease == 3: self._rescheduleAsRev(card, conf, True) - self.lrnRepCount -= lastLeft leaving = True + # graduation time? elif ease == 2 and card.left-1 <= 0: self._rescheduleAsRev(card, conf, False) - self.lrnRepCount -= 1 leaving = True else: + # one step towards graduation if ease == 2: card.left -= 1 - self.lrnRepCount -= 1 + # failed else: card.left = self._startingLeft(card) - self.lrnRepCount += card.left - lastLeft + self.lrnCount += card.left delay = self._delayForGrade(conf, card.left) if card.due < time.time(): # not collapsed; add some randomness delay *= random.uniform(1, 1.25) card.due = int(time.time() + delay) heappush(self._lrnQueue, (card.due, card.id)) - self.lrnCount += 1 self._logLrn(card, ease, conf, leaving, type, lastLeft) def _delayForGrade(self, conf, left): @@ -501,7 +496,7 @@ did in %s and queue = 2 and due <= :lim %s limit %d""" % ( def _getRevCard(self): if self._fillRev(): self.revCount -= 1 - return self._revQueue.pop() + return self.col.getCard(self._revQueue.pop()) def _revOrder(self): if self.col.conf['revOrder']: @@ -531,8 +526,7 @@ did in %s and queue = 2 and due <= :lim %s limit %d""" % ( card.due = int(self._delayForGrade(conf, 0) + time.time()) card.left = len(conf['delays']) card.queue = 1 - self.lrnCount += 1 - self.lrnRepCount += card.left + self.lrnCount += card.left # leech? if not self._checkLeech(card, conf) and conf['relearn']: heappush(self._lrnQueue, (card.due, card.id)) diff --git a/anki/sync.py b/anki/sync.py index 795fcb514..7806af3f0 100644 --- a/anki/sync.py +++ b/anki/sync.py @@ -140,7 +140,7 @@ select count() from notes where id not in (select distinct nid from cards)""") assert m['usn'] != -1 self.col.sched.reset() return [ - list(self.col.sched.repCounts()), + list(self.col.sched.counts()), self.col.db.scalar("select count() from cards"), self.col.db.scalar("select count() from notes"), self.col.db.scalar("select count() from revlog"), diff --git a/tests/test_sched.py b/tests/test_sched.py index ac11c3951..d74f44926 100644 --- a/tests/test_sched.py +++ b/tests/test_sched.py @@ -426,10 +426,10 @@ def test_cram(): conf = d.sched._lrnConf(c) conf['reset'] = False conf['resched'] = False - assert d.sched.cardCounts() == (1, 0, 0) + assert d.sched.counts() == (1, 0, 0) c = d.sched.getCard() d.sched._cardConf(c)['cram']['delays'] = [0.5, 3, 10] - assert d.sched.cardCounts() == (0, 0, 0) + assert d.sched.counts() == (0, 0, 0) # check that estimates work assert d.sched.nextIvl(c, 1) == 30 assert d.sched.nextIvl(c, 2) == 180 @@ -439,7 +439,7 @@ def test_cram(): d.sched.answerCard(c, 1) assert c.ivl == 100 # and should have incremented lrn count - assert d.sched.cardCounts()[1] == 1 + assert d.sched.counts()[1] == 1 # reset ivl for exit test, and pass card d.sched.answerCard(c, 2) delta = c.due - time.time() @@ -453,7 +453,7 @@ def test_cram(): assert c.due == d.sched.today + c.ivl # and if the queue is reset, it shouldn't appear in the new queue again d.reset() - assert d.sched.cardCounts() == (0, 0, 0) + assert d.sched.counts() == (0, 0, 0) # now try again with ivl rescheduling c = copy.copy(cardcopy) c.flush() @@ -484,7 +484,7 @@ def test_cram(): # users should be able to cram entire deck too d.conf['decks'] = [] d.cramDecks() - assert d.sched.cardCounts()[0] > 0 + assert d.sched.counts()[0] > 0 def test_cramLimits(): d = getEmptyDeck() @@ -500,29 +500,29 @@ def test_cramLimits(): # the default cram should return all three d.conf['decks'] = [1] d.cramDecks() - assert d.sched.cardCounts()[0] == 3 + assert d.sched.counts()[0] == 3 # if we start from the day after tomorrow, it should be 2 d.cramDecks(min=1) - assert d.sched.cardCounts()[0] == 2 + assert d.sched.counts()[0] == 2 # or after 2 days d.cramDecks(min=2) - assert d.sched.cardCounts()[0] == 1 + assert d.sched.counts()[0] == 1 # we may get nothing d.cramDecks(min=3) - assert d.sched.cardCounts()[0] == 0 + assert d.sched.counts()[0] == 0 # tomorrow(0) + dayAfter(1) = 2 d.cramDecks(max=1) - assert d.sched.cardCounts()[0] == 2 + assert d.sched.counts()[0] == 2 # if max is tomorrow, we get only one d.cramDecks(max=0) - assert d.sched.cardCounts()[0] == 1 + assert d.sched.counts()[0] == 1 # both should work d.cramDecks(min=0, max=0) - assert d.sched.cardCounts()[0] == 1 + assert d.sched.counts()[0] == 1 d.cramDecks(min=1, max=1) - assert d.sched.cardCounts()[0] == 1 + assert d.sched.counts()[0] == 1 d.cramDecks(min=0, max=1) - assert d.sched.cardCounts()[0] == 2 + assert d.sched.counts()[0] == 2 def test_adjIvl(): d = getEmptyDeck() @@ -617,54 +617,27 @@ def test_ordcycle(): assert d.sched.getCard().ord == 1 assert d.sched.getCard().ord == 2 -def test_cardcounts(): - d = getEmptyDeck() - # add a second deck - grp = d.decks.id("Default::new deck") - # for each card type - for type in range(3): - # and each of the decks - for did in (1,grp): - # create a new note - f = d.newNote() - f['Front'] = u"one" - d.addNote(f) - c = f.cards()[0] - # set type/did - c.type = type - c.queue = type - c.did = did - c.due = 0 - c.flush() - d.reset() - # with the default settings, there's no count limit - assert d.sched.cardCounts() == (2,2,2) - # check limit to one deck - d.decks.select(grp) - d.reset() - assert d.sched.cardCounts() == (1,1,1) - def test_counts_idx(): d = getEmptyDeck() f = d.newNote() f['Front'] = u"one"; f['Back'] = u"two" d.addNote(f) d.reset() - assert d.sched.cardCounts() == (1, 0, 0) + assert d.sched.counts() == (1, 0, 0) c = d.sched.getCard() # counter's been decremented but idx indicates 1 - assert d.sched.cardCounts() == (0, 0, 0) + assert d.sched.counts() == (0, 0, 0) assert d.sched.countIdx(c) == 0 # answer to move to learn queue d.sched.answerCard(c, 1) - assert d.sched.cardCounts() == (0, 1, 0) + assert d.sched.counts() == (0, 2, 0) # fetching again will decrement the count c = d.sched.getCard() - assert d.sched.cardCounts() == (0, 0, 0) + assert d.sched.counts() == (0, 0, 0) assert d.sched.countIdx(c) == 1 # answering should add it back again d.sched.answerCard(c, 1) - assert d.sched.cardCounts() == (0, 1, 0) + assert d.sched.counts() == (0, 2, 0) def test_repCounts(): d = getEmptyDeck() @@ -673,37 +646,37 @@ def test_repCounts(): d.addNote(f) d.reset() # lrnReps should be accurate on pass/fail - assert d.sched.repCounts() == (1, 0, 0) + assert d.sched.counts() == (1, 0, 0) d.sched.answerCard(d.sched.getCard(), 1) - assert d.sched.repCounts() == (0, 2, 0) + assert d.sched.counts() == (0, 2, 0) d.sched.answerCard(d.sched.getCard(), 1) - assert d.sched.repCounts() == (0, 2, 0) + assert d.sched.counts() == (0, 2, 0) d.sched.answerCard(d.sched.getCard(), 2) - assert d.sched.repCounts() == (0, 1, 0) + assert d.sched.counts() == (0, 1, 0) d.sched.answerCard(d.sched.getCard(), 1) - assert d.sched.repCounts() == (0, 2, 0) + assert d.sched.counts() == (0, 2, 0) d.sched.answerCard(d.sched.getCard(), 2) - assert d.sched.repCounts() == (0, 1, 0) + assert d.sched.counts() == (0, 1, 0) d.sched.answerCard(d.sched.getCard(), 2) - assert d.sched.repCounts() == (0, 0, 0) + assert d.sched.counts() == (0, 0, 0) f = d.newNote() f['Front'] = u"two" d.addNote(f) d.reset() # initial pass should be correct too d.sched.answerCard(d.sched.getCard(), 2) - assert d.sched.repCounts() == (0, 1, 0) + assert d.sched.counts() == (0, 1, 0) d.sched.answerCard(d.sched.getCard(), 1) - assert d.sched.repCounts() == (0, 2, 0) + assert d.sched.counts() == (0, 2, 0) d.sched.answerCard(d.sched.getCard(), 3) - assert d.sched.repCounts() == (0, 0, 0) + assert d.sched.counts() == (0, 0, 0) # immediate graduate should work f = d.newNote() f['Front'] = u"three" d.addNote(f) d.reset() d.sched.answerCard(d.sched.getCard(), 3) - assert d.sched.repCounts() == (0, 0, 0) + assert d.sched.counts() == (0, 0, 0) # and failing a review should too f = d.newNote() f['Front'] = u"three" @@ -714,9 +687,9 @@ def test_repCounts(): c.due = d.sched.today c.flush() d.reset() - assert d.sched.repCounts() == (0, 0, 1) + assert d.sched.counts() == (0, 0, 1) d.sched.answerCard(d.sched.getCard(), 1) - assert d.sched.repCounts() == (0, 2, 0) + assert d.sched.counts() == (0, 2, 0) def test_timing(): d = getEmptyDeck() @@ -836,7 +809,7 @@ def test_deckFlow(): d.addNote(f) # should get top level one first, then ::1, then ::2 d.reset() - assert d.sched.cardCounts() == (3,0,0) + assert d.sched.counts() == (3,0,0) for i in "one", "three", "two": c = d.sched.getCard() assert c.note()['Front'] == i @@ -889,10 +862,10 @@ def test_forget(): c.queue = 2; c.type = 2; c.ivl = 100; c.due = 0 c.flush() d.reset() - assert d.sched.cardCounts() == (0, 0, 1) + assert d.sched.counts() == (0, 0, 1) d.sched.forgetCards([c.id]) d.reset() - assert d.sched.cardCounts() == (1, 0, 0) + assert d.sched.counts() == (1, 0, 0) def test_resched(): d = getEmptyDeck() @@ -918,12 +891,12 @@ def test_revlim(): d.addNote(f) d.db.execute("update cards set due = 0, queue = 2, type = 2") d.reset() - assert d.sched.repCounts()[2] == 20 + assert d.sched.counts()[2] == 20 for i in range(5): d.sched.answerCard(d.sched.getCard(), 3) - assert d.sched.repCounts()[2] == 15 + assert d.sched.counts()[2] == 15 t = d.decks.top() t['revLim'] = 10 d.reset() - assert d.sched.repCounts()[2] == 5 + assert d.sched.counts()[2] == 5 diff --git a/tests/test_undo.py b/tests/test_undo.py index 702435b11..ff0886645 100644 --- a/tests/test_undo.py +++ b/tests/test_undo.py @@ -44,18 +44,18 @@ def test_review(): d.reset() assert not d.undoName() # answer - assert d.sched.cardCounts() == (1, 0, 0) + assert d.sched.counts() == (1, 0, 0) c = d.sched.getCard() assert c.queue == 0 d.sched.answerCard(c, 2) assert c.left == 1 - assert d.sched.cardCounts() == (0, 1, 0) + assert d.sched.counts() == (0, 1, 0) assert c.queue == 1 # undo assert d.undoName() d.undo() d.reset() - assert d.sched.cardCounts() == (1, 0, 0) + assert d.sched.counts() == (1, 0, 0) c.load() assert c.queue == 0 assert c.left != 1 @@ -64,18 +64,18 @@ def test_review(): f['Front'] = u"two" d.addNote(f) d.reset() - assert d.sched.cardCounts() == (2, 0, 0) + assert d.sched.counts() == (2, 0, 0) c = d.sched.getCard() d.sched.answerCard(c, 2) c = d.sched.getCard() d.sched.answerCard(c, 2) - assert d.sched.cardCounts() == (0, 2, 0) + assert d.sched.counts() == (0, 2, 0) d.undo() d.reset() - assert d.sched.cardCounts() == (1, 1, 0) + assert d.sched.counts() == (1, 1, 0) d.undo() d.reset() - assert d.sched.cardCounts() == (2, 0, 0) + assert d.sched.counts() == (2, 0, 0) # performing a normal op will clear the review queue c = d.sched.getCard() d.sched.answerCard(c, 2) diff --git a/tests/test_upgrade.py b/tests/test_upgrade.py index e5aa89056..71929177f 100644 --- a/tests/test_upgrade.py +++ b/tests/test_upgrade.py @@ -28,7 +28,7 @@ def test_upgrade(): # 3 new, 2 failed, 1 due deck.reset() deck.conf['counts'] = COUNT_REMAINING - assert deck.sched.cardCounts() == (3,2,1) + assert deck.sched.counts() == (3,4,1) # now's a good time to test the integrity check too deck.fixIntegrity() # c = deck.sched.getCard()