-
Notifications
You must be signed in to change notification settings - Fork 16
/
cards.py
133 lines (114 loc) · 3.88 KB
/
cards.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
"""
Flashcard data and operations.
"""
import datetime
import math
import random
import itertools
time_fmt = "%Y-%m-%d"
class card:
def __init__(self, top, bot, time, repetitions=0, interval=1, easiness=2.5):
self.top = top
self.bot = bot
self.time = time.replace(second=0, microsecond=0)
self.repetitions = repetitions
self.interval = interval
self.easiness = easiness
self.filename = None # Reserved for use by {load,save}_all
def is_new(self):
return self.repetitions == 0
def next_time(self):
return self.time + datetime.timedelta(days=math.ceil(self.interval))
def repeat(self, quality, time):
# SM-2
assert quality >= 0 and quality <= 5
self.easiness = max(1.3, self.easiness + 0.1 - (5.0 - quality) * (0.08 + (5.0 - quality) * 0.02))
if quality < 3: self.repetitions = 0
else: self.repetitions += 1
if self.repetitions == 1: self.interval = 1
elif self.repetitions == 2: self.interval = 6
else: self.interval *= self.easiness
self.time = time
is_new = property(is_new)
next_time = property(next_time)
def save(output, cards):
for card in cards:
parts = (card.time.strftime(time_fmt), str(card.repetitions), str(card.interval), str(card.easiness), card.top, card.bot)
print >> output, '\t'.join(parts)
def load(input):
for line in input:
parts = line.strip().split('\t')
if len(parts) != 6:
raise IOError("wrong number of records on line")
time, repetitions, interval, easiness, top, bot = parts
time = datetime.datetime.strptime(time, time_fmt)
repetitions = int(repetitions)
interval = float(interval)
easiness = float(easiness)
new = card(top, bot, time, repetitions, interval, easiness)
yield new
def save_all(cards):
import argopen
outputs = {}
try:
for card in cards:
if card.filename not in outputs:
outputs[card.filename] = argopen.open(card.filename, 'w')
save(outputs[card.filename], [card])
finally:
for output in outputs.values():
output.close()
def load_all(filenames):
import argopen
for filename in filenames:
with argopen.open(filename) as input:
for card in load(input):
card.filename = filename
yield card
def fetch_cards(cards, now, max_reviews=None, max_new=None, randomize=False):
new_cards = list(itertools.islice((c for c in cards if c.is_new), max_new))
new_cards.reverse()
to_review = list(itertools.islice((c for c in cards if not c.is_new and c.next_time <= now), max_reviews))
to_review.reverse()
if randomize:
random.shuffle(to_review)
def choose_next():
if len(to_review) > 0:
return to_review.pop()
elif len(new_cards) > 0:
return new_cards.pop()
else:
return None
def reject_card(card):
if card.is_new:
new_cards.insert(0, card)
else:
to_review.insert(0, card)
return choose_next, reject_card
def run_cards(cards, now, review_card, max_reviews=None, max_new=None, randomize=False):
choose_next, reject_card = fetch_cards(cards, now, max_reviews, max_new, randomize)
while True:
current = choose_next()
if current is None:
break
quality = review_card(current)
current.repeat(quality, now)
if current.is_new:
reject_card(current)
def bulk_review(cards, now, batch_size, show_batch, review_card, max_reviews=None, max_new=None, randomize=False):
import random
choose_next, reject_card = fetch_cards(cards, now, max_reviews, max_new, randomize)
batch = [n for i in range(batch_size) for n in [choose_next()] if n is not None]
while len(batch) > 0:
random.shuffle(batch)
show_batch(batch)
def run_card(card):
quality = review_card(card)
card.repeat(quality, now)
return quality
batch = [c for c in batch for r in [run_card(c)] if c.is_new]
while len(batch) < batch_size:
next = choose_next()
if next is None:
break
batch.append(next)