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
This commit is contained in:
Damien Elmes 2011-12-05 19:15:46 +09:00
parent e7101faff7
commit 8308c79fa6
5 changed files with 77 additions and 110 deletions

View file

@ -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))

View file

@ -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"),

View file

@ -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

View file

@ -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)

View file

@ -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()