encode daily steps in left

When a user has learning steps that extend past the daily cutoff, we end up
counting them all instead of only the ones that would be done today. In order
to avoid this without expensive calculations or db schema changes, we
calculate the number of steps until the daily cutoff and pack it into the left
column, as totalLeft + leftToday*1000.
This commit is contained in:
Damien Elmes 2012-05-20 13:55:49 +09:00
parent 33407a6043
commit 32fde2a072
6 changed files with 45 additions and 19 deletions

View file

@ -42,10 +42,10 @@ MODEL_STD = 0
MODEL_CLOZE = 1
# deck schema & syncing vars
SCHEMA_VERSION = 9
SCHEMA_VERSION = 10
SYNC_ZIP_SIZE = int(2.5*1024*1024)
SYNC_URL = os.environ.get("SYNC_URL") or "https://beta.ankiweb.net/sync/"
SYNC_VER = 3
SYNC_VER = 4
# Labels
##########################################################################

View file

@ -88,7 +88,7 @@ class Scheduler(object):
if card:
idx = self.countIdx(card)
if idx == 1:
counts[1] += card.left
counts[1] += card.left/1000
else:
counts[idx] += 1
return tuple(counts)
@ -388,7 +388,7 @@ select count() from
def _resetLrnCount(self):
self.lrnCount = self.col.db.scalar("""
select sum(left) from (select left from cards where
select sum(left/1000) from (select left from cards where
did in %s and queue = 1 and due < ? limit %d)""" % (
self._deckLimit(), self.reportLimit),
self.dayCutoff) or 0
@ -418,7 +418,7 @@ limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff)
if self._lrnQueue[0][0] < cutoff:
id = heappop(self._lrnQueue)[1]
card = self.col.getCard(id)
self.lrnCount -= card.left
self.lrnCount -= card.left/1000
return card
def _answerLrnCard(self, card, ease):
@ -438,13 +438,15 @@ limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff)
self._rescheduleAsRev(card, conf, True)
leaving = True
# graduation time?
elif ease == 2 and card.left-1 <= 0:
elif ease == 2 and (card.left%1000)-1 <= 0:
self._rescheduleAsRev(card, conf, False)
leaving = True
else:
# one step towards graduation
if ease == 2:
card.left -= 1
# decrement real left count and recalculate left today
left = (card.left % 1000) - 1
card.left = self._leftToday(conf['delays'], left)*1000 + left
# failed
else:
card.left = self._startingLeft(card)
@ -462,7 +464,7 @@ limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff)
delay *= random.uniform(1, 1.25)
card.due = int(time.time() + delay)
if card.due < self.dayCutoff:
self.lrnCount += card.left
self.lrnCount += card.left/1000
# if the queue is not empty and there's nothing else to do, make
# sure we don't put it at the head of the queue and end up showing
# it twice in a row
@ -473,6 +475,7 @@ limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff)
self._logLrn(card, ease, conf, leaving, type, lastLeft)
def _delayForGrade(self, conf, left):
left = left % 1000
try:
delay = conf['delays'][-left]
except IndexError:
@ -501,7 +504,22 @@ limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff)
def _startingLeft(self, card):
conf = self._lrnConf(card)
return len(conf['delays'])
tot = len(conf['delays'])
tod = self._leftToday(conf['delays'], tot)
return tot + tod*1000
def _leftToday(self, delays, left, now=None):
"The number of steps that can be completed by the day cutoff."
if not now:
now = intTime()
delays = delays[-left:]
ok = 0
for i in range(len(delays)):
now += delays[i]*60
if now > self.dayCutoff:
break
ok = i
return ok+1
def _graduatingIvl(self, card, conf, early, adj=True):
if card.type == 2:
@ -565,7 +583,7 @@ where queue = 1 and type = 2
def _lrnForDeck(self, did):
return self.col.db.scalar(
"""
select sum(left) from
select sum(left/1000) from
(select left from cards where did = ? and queue = 1 and due < ? limit ?)""",
did, intTime() + self.col.conf['collapseTime'], self.reportLimit) or 0
@ -662,8 +680,9 @@ did = ? and queue = 2 and due <= ? limit ?""",
card.odue = card.due
card.due = int(self._delayForGrade(conf, 0) + time.time())
card.left = len(conf['delays'])
card.left += self._leftToday(conf['delays'], card.left)*1000
card.queue = 1
self.lrnCount += card.left
self.lrnCount += card.left/1000
# leech?
if not self._checkLeech(card, conf) and conf['delays']:
heappush(self._lrnQueue, (card.due, card.id))
@ -1024,7 +1043,7 @@ your short-term review workload will become."""))
# early removal
return self._graduatingIvl(card, conf, True, adj=False) * 86400
else:
left = card.left - 1
left = card.left%1000 - 1
if left <= 0:
# graduate
return self._graduatingIvl(card, conf, False, adj=False) * 86400

View file

@ -135,6 +135,10 @@ def _upgrade(col, ver):
if changed:
col.media.db.commit()
col.db.execute("update col set ver = 9")
if ver < 10:
col.db.execute("""
update cards set left = left + left*1000 where queue = 1""")
col.db.execute("update col set ver = 10")
def _upgradeClozeModel(col, m):
m['type'] = MODEL_CLOZE

View file

@ -682,7 +682,7 @@ and ord = ? limit 1""", m['id'], t['ord']):
for t in ("cards", "notes", "models", "media"):
col.db.execute("drop table if exists %sDeleted" % t)
# and failed cards
left = len(col.decks.confForDid(1)['new']['delays'])
left = len(col.decks.confForDid(1)['new']['delays'])*1001
col.db.execute("update cards set odue = ?, left=? where type = 1",
col.sched.today+1, left)
# and due cards

View file

@ -112,7 +112,8 @@ def test_learn():
# fail it
d.sched.answerCard(c, 1)
# it should have three reps left to graduation
assert c.left == 3
assert c.left%1000 == 3
assert c.left/1000 == 3
# it should by due in 30 seconds
t = round(c.due - time.time())
assert t >= 25 and t <= 40
@ -120,7 +121,8 @@ def test_learn():
d.sched.answerCard(c, 2)
# it should by due in 3 minutes
assert round(c.due - time.time()) in (179, 180)
assert c.left == 2
assert c.left%1000 == 2
assert c.left/1000 == 2
# check log is accurate
log = d.db.first("select * from revlog order by id desc")
assert log[3] == 2
@ -130,7 +132,8 @@ def test_learn():
d.sched.answerCard(c, 2)
# it should by due in 10 minutes
assert round(c.due - time.time()) in (599, 600)
assert c.left == 1
assert c.left%1000 == 1
assert c.left/1000 == 1
# the next pass should graduate the card
assert c.queue == 1
assert c.type == 1
@ -299,7 +302,7 @@ def test_overdue_lapse():
c.due = -1
c.odue = -1
c.factor = 2500
c.left = 2
c.left = 2002
c.ivl = 0
c.flush()
d.sched._clearOverdue = False

View file

@ -48,7 +48,7 @@ def test_review():
c = d.sched.getCard()
assert c.queue == 0
d.sched.answerCard(c, 2)
assert c.left == 1
assert c.left == 1001
assert d.sched.counts() == (0, 1, 0)
assert c.queue == 1
# undo
@ -58,7 +58,7 @@ def test_review():
assert d.sched.counts() == (1, 0, 0)
c.load()
assert c.queue == 0
assert c.left != 1
assert c.left != 1001
assert not d.undoName()
# we should be able to undo multiple answers too
f['Front'] = u"two"