forgetful 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/LICENSE +22 -0
- data/README.rdoc +5 -23
- data/Rakefile +44 -0
- data/VERSION +1 -0
- data/bin/forgetful +1 -2
- data/lib/forgetful/csv_ext/reminder.rb +50 -0
- data/lib/{reminder.rb → forgetful/reminder.rb} +4 -7
- data/lib/{supermemo.rb → forgetful/supermemo.rb} +0 -7
- data/lib/forgetful.rb +3 -3
- data/spec/reminder_csv_spec.rb +67 -0
- data/spec/reminder_spec.rb +69 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/supermemo_spec.rb +95 -0
- metadata +51 -43
- data/MIT-LICENSE +0 -21
- data/Manifest +0 -13
- data/forgetful.gemspec +0 -34
- data/lib/csv_ext/reminder.rb +0 -47
- data/test/test_reminder.rb +0 -99
- data/test/test_reminder_csv.rb +0 -97
- data/test/test_supermemo.rb +0 -85
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2010 Jonathan Palardy
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
=
|
1
|
+
= forgetful
|
2
|
+
|
3
|
+
(tested with ree-1.8.7 and 1.9.2dev)
|
2
4
|
|
3
5
|
A minimal command-line app implementing the SuperMemo 2 algorithm.
|
4
6
|
|
@@ -49,26 +51,6 @@ your CSV files under source control to keep a history.
|
|
49
51
|
|
50
52
|
Enjoy!
|
51
53
|
|
54
|
+
== Copyright
|
52
55
|
|
53
|
-
|
54
|
-
|
55
|
-
Copyright (c) 2008 Jonathan Palardy
|
56
|
-
|
57
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
58
|
-
a copy of this software and associated documentation files (the
|
59
|
-
"Software"), to deal in the Software without restriction, including
|
60
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
61
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
62
|
-
permit persons to whom the Software is furnished to do so, subject to
|
63
|
-
the following conditions:
|
64
|
-
|
65
|
-
The above copyright notice and this permission notice shall be
|
66
|
-
included in all copies or substantial portions of the Software.
|
67
|
-
|
68
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
69
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
70
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
71
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
72
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
73
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
74
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
56
|
+
Copyright (c) 2010 Jonathan Palardy. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "forgetful"
|
8
|
+
gem.summary = "A minimal command-line implementation of the SuperMemo 2 algorithm."
|
9
|
+
gem.description = "A minimal command-line implementation of the SuperMemo 2 algorithm."
|
10
|
+
gem.email = "jonathan.palardy@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/jpalardy/forgetful"
|
12
|
+
gem.authors = ["Jonathan Palardy"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
end
|
15
|
+
Jeweler::GemcutterTasks.new
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'spec/rake/spectask'
|
21
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
22
|
+
spec.libs << 'lib' << 'spec'
|
23
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
24
|
+
end
|
25
|
+
|
26
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
27
|
+
spec.libs << 'lib' << 'spec'
|
28
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
29
|
+
spec.rcov = true
|
30
|
+
end
|
31
|
+
|
32
|
+
task :spec => :check_dependencies
|
33
|
+
|
34
|
+
task :default => :spec
|
35
|
+
|
36
|
+
require 'rake/rdoctask'
|
37
|
+
Rake::RDocTask.new do |rdoc|
|
38
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
39
|
+
|
40
|
+
rdoc.rdoc_dir = 'rdoc'
|
41
|
+
rdoc.title = "forgetful #{version}"
|
42
|
+
rdoc.rdoc_files.include('README*')
|
43
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
44
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.1
|
data/bin/forgetful
CHANGED
@@ -0,0 +1,50 @@
|
|
1
|
+
require "csv"
|
2
|
+
if CSV.const_defined? :Reader
|
3
|
+
# Ruby 1.8 compatible
|
4
|
+
require 'rubygems'
|
5
|
+
require 'fastercsv'
|
6
|
+
Object.send(:remove_const, :CSV)
|
7
|
+
CSV = FasterCSV
|
8
|
+
end
|
9
|
+
|
10
|
+
class Reminder
|
11
|
+
def to_csv
|
12
|
+
if history.empty?
|
13
|
+
[question, answer, due_on.to_s]
|
14
|
+
else
|
15
|
+
[question, answer, due_on.to_s, history.join]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.read_csv(filename)
|
20
|
+
File.open(filename) do |file|
|
21
|
+
self.parse_csv(file)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.parse_csv(io)
|
26
|
+
converters = [lambda { |question| question },
|
27
|
+
lambda { |answer| answer },
|
28
|
+
lambda { |due_on| Date.parse(due_on) },
|
29
|
+
lambda { |history| history.scan(/./).collect { |x| x.to_i } }]
|
30
|
+
|
31
|
+
CSV.parse(io, :skip_blanks => true).collect do |list|
|
32
|
+
list = list.zip(converters).collect { |col, converter| converter[col] }
|
33
|
+
new(*list)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.write_csv(filename, reminders)
|
38
|
+
File.open(filename, "w") do |file|
|
39
|
+
file.write(generate_csv(reminders))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.generate_csv(reminders)
|
44
|
+
CSV.generate do |csv|
|
45
|
+
reminders.each do |reminder|
|
46
|
+
csv << reminder.to_csv
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
class Reminder
|
3
2
|
attr_reader :question, :answer, :due_on, :history
|
4
3
|
|
@@ -10,20 +9,18 @@ class Reminder
|
|
10
9
|
end
|
11
10
|
|
12
11
|
def next(q)
|
13
|
-
Reminder.new(
|
12
|
+
Reminder.new(question, answer, SuperMemo::next_date(Date.today, history + [q]), history + [q])
|
14
13
|
end
|
15
14
|
|
16
15
|
def to_a
|
17
|
-
[
|
16
|
+
[question, answer, due_on, history]
|
18
17
|
end
|
19
18
|
|
20
19
|
def <=>(other)
|
21
|
-
|
20
|
+
to_a <=> other.to_a
|
22
21
|
end
|
23
22
|
|
24
23
|
def review?
|
25
|
-
|
24
|
+
history.empty? || history.last < 4
|
26
25
|
end
|
27
|
-
|
28
26
|
end
|
29
|
-
|
@@ -1,6 +1,4 @@
|
|
1
|
-
|
2
1
|
module SuperMemo
|
3
|
-
|
4
2
|
def self.next_ef(q, ef)
|
5
3
|
return [ef + (0.1 - (5-q) * (0.08 + (5-q) * 0.02)), 1.3].max
|
6
4
|
end
|
@@ -13,8 +11,6 @@ module SuperMemo
|
|
13
11
|
return q < 3 ? 1 : {0 => 1, 1 => 6}.fetch(i, interval * ef).round
|
14
12
|
end
|
15
13
|
|
16
|
-
#-------------------------------------------------
|
17
|
-
|
18
14
|
# [] -> 2.5, 0, 0
|
19
15
|
# [5,5,5] -> 2.8, 3, 16
|
20
16
|
def self.traverse(qs, ef=2.5, i=0, interval=0)
|
@@ -28,7 +24,4 @@ module SuperMemo
|
|
28
24
|
ef, i, interval = traverse(qs)
|
29
25
|
return date + interval
|
30
26
|
end
|
31
|
-
|
32
27
|
end
|
33
|
-
|
34
|
-
|
data/lib/forgetful.rb
CHANGED
@@ -0,0 +1,67 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "reminder (csv)" do
|
4
|
+
describe "parsing the bare format" do
|
5
|
+
before do
|
6
|
+
csv = <<END
|
7
|
+
carbon,6
|
8
|
+
nitrogen,7
|
9
|
+
oxygen,8
|
10
|
+
END
|
11
|
+
@reminders = Reminder.parse_csv(csv)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should have the right number of lines" do
|
15
|
+
@reminders.size.should == 3
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should have the right data" do
|
19
|
+
@reminders[0].to_a.should == ['carbon', '6',Date.today, []]
|
20
|
+
@reminders[1].to_a.should == ['nitrogen','7',Date.today, []]
|
21
|
+
@reminders[2].to_a.should == ['oxygen', '8',Date.today, []]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "parsing the full format" do
|
26
|
+
before do
|
27
|
+
csv = <<END
|
28
|
+
carbon,6,2008-11-11,5
|
29
|
+
nitrogen,7,2008-11-11,535
|
30
|
+
|
31
|
+
oxygen,8,2008-11-11,5042
|
32
|
+
END
|
33
|
+
@reminders = Reminder.parse_csv(csv)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should have the right number of lines" do
|
37
|
+
@reminders.size.should == 3
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should have the right data" do
|
41
|
+
@reminders[0].to_a.should == ['carbon', '6',Date.parse('2008-11-11'), [5]]
|
42
|
+
@reminders[1].to_a.should == ['nitrogen','7',Date.parse('2008-11-11'), [5,3,5]]
|
43
|
+
@reminders[2].to_a.should == ['oxygen', '8',Date.parse('2008-11-11'), [5,0,4,2]]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "generating csv" do
|
48
|
+
before do
|
49
|
+
@reminders = []
|
50
|
+
@reminders << Reminder.new('carbon','6')
|
51
|
+
@reminders << Reminder.new('nitrogen','7').next(5).next(5).next(5)
|
52
|
+
@reminders << Reminder.new('oxygen','8').next(1).next(1).next(1)
|
53
|
+
@reminders << Reminder.new('fluorine','9').next(1).next(5).next(1).next(5)
|
54
|
+
@reminders << Reminder.new('neon','10',Date.today+5)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should generate appropriate csv" do
|
58
|
+
Reminder.generate_csv(@reminders).should == <<END
|
59
|
+
carbon,6,#{Date.today}
|
60
|
+
nitrogen,7,#{(Date.today+16)},555
|
61
|
+
oxygen,8,#{(Date.today+1)},111
|
62
|
+
fluorine,9,#{(Date.today+1)},1515
|
63
|
+
neon,10,#{(Date.today+5)}
|
64
|
+
END
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "reminder" do
|
4
|
+
before do
|
5
|
+
@question = 'carbon'
|
6
|
+
@answer = '6'
|
7
|
+
@due_on = Date.today - 2
|
8
|
+
@history = [5,5,5]
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "with default constructor" do
|
12
|
+
before do
|
13
|
+
@reminder = Reminder.new(@question, @answer)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should be valid" do
|
17
|
+
@reminder.to_a.should == [@question, @answer, Date.today, []]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "with full constructor" do
|
22
|
+
before do
|
23
|
+
@reminder = Reminder.new(@question, @answer, @due_on, @history)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should be valid" do
|
27
|
+
@reminder.to_a.should == [@question, @answer, @due_on, @history]
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should not allow its history to be modified" do
|
31
|
+
error_type = RUBY_VERSION =~ /^1.8/ ? TypeError : RuntimeError
|
32
|
+
lambda {
|
33
|
+
@reminder.history.push(5)
|
34
|
+
}.should raise_error(error_type, "can't modify frozen array")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "next" do
|
39
|
+
before do
|
40
|
+
@quality = 5
|
41
|
+
@reminder = Reminder.new(@question, @answer, @due_on, @history)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should create a new reminder scheduled appropriately" do
|
45
|
+
@reminder.next(@quality).to_a.should == [@question, @answer, Date.today+45, @history + [@quality]]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "review (w/o history)" do
|
50
|
+
subject { Reminder.new(@question, @answer) }
|
51
|
+
|
52
|
+
it { should be_review}
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "review (w/ history)" do
|
56
|
+
before do
|
57
|
+
@reminder = Reminder.new(@question, @answer, @due_on, @history)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should qualify for review if quality < 4" do
|
61
|
+
@reminder.next(0).should be_review
|
62
|
+
@reminder.next(1).should be_review
|
63
|
+
@reminder.next(2).should be_review
|
64
|
+
@reminder.next(3).should be_review
|
65
|
+
@reminder.next(4).should_not be_review
|
66
|
+
@reminder.next(5).should_not be_review
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color --backtrace
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "supermemo" do
|
4
|
+
describe "next_i" do
|
5
|
+
qs = [5,4,3,2,1,0]
|
6
|
+
results = [4,4,4,0,0,0]
|
7
|
+
|
8
|
+
qs.zip(results).each do |q,result|
|
9
|
+
it "-- next_i(#{q}, 3) = #{result}" do
|
10
|
+
SuperMemo::next_i(q, 3).should == result
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "next_interval (for i = 0)" do
|
16
|
+
qs = [5,4,3,2,1,0]
|
17
|
+
results = [1,1,1,1,1,1]
|
18
|
+
|
19
|
+
qs.zip(results).each do |q,result|
|
20
|
+
it "-- next_interval(#{q}, 2.5, 0, 0) = #{result}" do
|
21
|
+
SuperMemo::next_interval(q, 2.5, 0, 0).should == result
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "next_interval (for i = 1)" do
|
27
|
+
qs = [5,4,3,2,1,0]
|
28
|
+
results = [6,6,6,1,1,1]
|
29
|
+
|
30
|
+
qs.zip(results).each do |q,result|
|
31
|
+
it "-- next_interval(#{q}, 2.5, 1, 1) = #{result}" do
|
32
|
+
SuperMemo::next_interval(q, 2.5, 1, 1).should == result
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "next_interval (for i > 1)" do
|
38
|
+
qs = [5,4,3,2,1,0]
|
39
|
+
results = [16,16,16,1,1,1]
|
40
|
+
|
41
|
+
qs.zip(results).each do |q,result|
|
42
|
+
it "-- next_interval(#{q}, 2.7, 2, 6) = #{result}" do
|
43
|
+
SuperMemo::next_interval(q, 2.7, 2, 6).should == result
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "next_interval (for changing efs)" do
|
49
|
+
efs = [1.3,2.0,2.5,2.6,2.7]
|
50
|
+
results = [8,12,15,16,16]
|
51
|
+
|
52
|
+
efs.zip(results).each do |ef,result|
|
53
|
+
it "-- next_interval(5, #{ef}, 2, 6) = #{result}" do
|
54
|
+
SuperMemo::next_interval(5, ef, 2, 6).should == result
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "next_ef (normal)" do
|
60
|
+
qs = [5,4,3,2,1,0]
|
61
|
+
results = [2.6,2.5,2.36,2.18,1.96,1.7]
|
62
|
+
|
63
|
+
qs.zip(results).each do |q,result|
|
64
|
+
it "-- next_ef(q, 2.5) = #{result}" do
|
65
|
+
SuperMemo::next_ef(q, 2.5).should be_close(result, 0.00001)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "next_ef (minimum)" do
|
71
|
+
qs = [5,4,3,2,1,0]
|
72
|
+
results = [1.4,1.3,1.3,1.3,1.3,1.3]
|
73
|
+
|
74
|
+
qs.zip(results).each do |q,result|
|
75
|
+
it "-- next_ef(q, 1.3) = #{result}" do
|
76
|
+
SuperMemo::next_ef(q, 1.3).should be_close(result, 0.00001)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "traverse" do
|
82
|
+
histories = [[], [5], [5,5], [5,5,5], [5,5,5,5]]
|
83
|
+
results = [[2.5,0,0], [2.6,1,1], [2.7,2,6], [2.8,3,16], [2.9,4,45]]
|
84
|
+
|
85
|
+
histories.zip(results).each do |history,result|
|
86
|
+
it "-- traverse(#{history.inspect}) = #{result.inspect}" do
|
87
|
+
ef, i, interval = SuperMemo::traverse(history)
|
88
|
+
|
89
|
+
ef.should be_close(result[0], 0.00001)
|
90
|
+
i.should == result[1]
|
91
|
+
interval.should == result[2]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: forgetful
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 2
|
8
|
+
- 1
|
9
|
+
version: 0.2.1
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Jonathan Palardy
|
@@ -9,22 +14,24 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date:
|
13
|
-
default_executable:
|
17
|
+
date: 2010-05-02 00:00:00 -04:00
|
18
|
+
default_executable: forgetful
|
14
19
|
dependencies:
|
15
20
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
17
|
-
|
18
|
-
|
19
|
-
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
20
25
|
requirements:
|
21
26
|
- - ">="
|
22
27
|
- !ruby/object:Gem::Version
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 2
|
31
|
+
- 9
|
32
|
+
version: 1.2.9
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
28
35
|
description: A minimal command-line implementation of the SuperMemo 2 algorithm.
|
29
36
|
email: jonathan.palardy@gmail.com
|
30
37
|
executables:
|
@@ -32,59 +39,60 @@ executables:
|
|
32
39
|
extensions: []
|
33
40
|
|
34
41
|
extra_rdoc_files:
|
35
|
-
-
|
36
|
-
- lib/csv_ext/reminder.rb
|
37
|
-
- lib/forgetful.rb
|
38
|
-
- lib/reminder.rb
|
39
|
-
- lib/supermemo.rb
|
42
|
+
- LICENSE
|
40
43
|
- README.rdoc
|
41
44
|
files:
|
45
|
+
- .gitignore
|
46
|
+
- LICENSE
|
47
|
+
- README.rdoc
|
48
|
+
- Rakefile
|
49
|
+
- VERSION
|
42
50
|
- bin/forgetful
|
43
51
|
- examples/katakana_romanji.csv
|
44
52
|
- examples/romanji_katakana.csv
|
45
|
-
- lib/csv_ext/reminder.rb
|
46
53
|
- lib/forgetful.rb
|
47
|
-
- lib/reminder.rb
|
48
|
-
- lib/
|
49
|
-
-
|
50
|
-
-
|
51
|
-
-
|
52
|
-
-
|
53
|
-
-
|
54
|
-
-
|
55
|
-
|
56
|
-
has_rdoc: false
|
54
|
+
- lib/forgetful/csv_ext/reminder.rb
|
55
|
+
- lib/forgetful/reminder.rb
|
56
|
+
- lib/forgetful/supermemo.rb
|
57
|
+
- spec/reminder_csv_spec.rb
|
58
|
+
- spec/reminder_spec.rb
|
59
|
+
- spec/spec.opts
|
60
|
+
- spec/spec_helper.rb
|
61
|
+
- spec/supermemo_spec.rb
|
62
|
+
has_rdoc: true
|
57
63
|
homepage: http://github.com/jpalardy/forgetful
|
64
|
+
licenses: []
|
65
|
+
|
58
66
|
post_install_message:
|
59
67
|
rdoc_options:
|
60
|
-
- --
|
61
|
-
- --inline-source
|
62
|
-
- --title
|
63
|
-
- Forgetful
|
64
|
-
- --main
|
65
|
-
- README.rdoc
|
68
|
+
- --charset=UTF-8
|
66
69
|
require_paths:
|
67
70
|
- lib
|
68
71
|
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
69
73
|
requirements:
|
70
74
|
- - ">="
|
71
75
|
- !ruby/object:Gem::Version
|
76
|
+
segments:
|
77
|
+
- 0
|
72
78
|
version: "0"
|
73
|
-
version:
|
74
79
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
75
81
|
requirements:
|
76
82
|
- - ">="
|
77
83
|
- !ruby/object:Gem::Version
|
78
|
-
|
79
|
-
|
84
|
+
segments:
|
85
|
+
- 0
|
86
|
+
version: "0"
|
80
87
|
requirements: []
|
81
88
|
|
82
|
-
rubyforge_project:
|
83
|
-
rubygems_version: 1.
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 1.3.6.1
|
84
91
|
signing_key:
|
85
|
-
specification_version:
|
92
|
+
specification_version: 3
|
86
93
|
summary: A minimal command-line implementation of the SuperMemo 2 algorithm.
|
87
94
|
test_files:
|
88
|
-
-
|
89
|
-
-
|
90
|
-
-
|
95
|
+
- spec/reminder_csv_spec.rb
|
96
|
+
- spec/reminder_spec.rb
|
97
|
+
- spec/spec_helper.rb
|
98
|
+
- spec/supermemo_spec.rb
|
data/MIT-LICENSE
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
Copyright (c) 2008 Jonathan Palardy
|
2
|
-
|
3
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
-
a copy of this software and associated documentation files (the
|
5
|
-
"Software"), to deal in the Software without restriction, including
|
6
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
-
permit persons to whom the Software is furnished to do so, subject to
|
9
|
-
the following conditions:
|
10
|
-
|
11
|
-
The above copyright notice and this permission notice shall be
|
12
|
-
included in all copies or substantial portions of the Software.
|
13
|
-
|
14
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
-
|
data/Manifest
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
bin/forgetful
|
2
|
-
examples/katakana_romanji.csv
|
3
|
-
examples/romanji_katakana.csv
|
4
|
-
lib/csv_ext/reminder.rb
|
5
|
-
lib/forgetful.rb
|
6
|
-
lib/reminder.rb
|
7
|
-
lib/supermemo.rb
|
8
|
-
Manifest
|
9
|
-
MIT-LICENSE
|
10
|
-
README.rdoc
|
11
|
-
test/test_reminder.rb
|
12
|
-
test/test_reminder_csv.rb
|
13
|
-
test/test_supermemo.rb
|
data/forgetful.gemspec
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
Gem::Specification.new do |s|
|
2
|
-
s.name = %q{forgetful}
|
3
|
-
s.version = "0.1.0"
|
4
|
-
|
5
|
-
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
6
|
-
s.authors = ["Jonathan Palardy"]
|
7
|
-
s.date = %q{2008-11-21}
|
8
|
-
s.default_executable = %q{forgetful}
|
9
|
-
s.description = %q{A minimal command-line implementation of the SuperMemo 2 algorithm.}
|
10
|
-
s.email = %q{jonathan.palardy@gmail.com}
|
11
|
-
s.executables = ["forgetful"]
|
12
|
-
s.extra_rdoc_files = ["bin/forgetful", "lib/csv_ext/reminder.rb", "lib/forgetful.rb", "lib/reminder.rb", "lib/supermemo.rb", "README.rdoc"]
|
13
|
-
s.files = ["bin/forgetful", "examples/katakana_romanji.csv", "examples/romanji_katakana.csv", "lib/csv_ext/reminder.rb", "lib/forgetful.rb", "lib/reminder.rb", "lib/supermemo.rb", "Manifest", "MIT-LICENSE", "README.rdoc", "test/test_reminder.rb", "test/test_reminder_csv.rb", "test/test_supermemo.rb", "forgetful.gemspec"]
|
14
|
-
s.homepage = %q{http://github.com/jpalardy/forgetful}
|
15
|
-
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Forgetful", "--main", "README.rdoc"]
|
16
|
-
s.require_paths = ["lib"]
|
17
|
-
s.rubyforge_project = %q{forgetful}
|
18
|
-
s.rubygems_version = %q{1.2.0}
|
19
|
-
s.summary = %q{A minimal command-line implementation of the SuperMemo 2 algorithm.}
|
20
|
-
s.test_files = ["test/test_reminder.rb", "test/test_reminder_csv.rb", "test/test_supermemo.rb"]
|
21
|
-
|
22
|
-
if s.respond_to? :specification_version then
|
23
|
-
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
24
|
-
s.specification_version = 2
|
25
|
-
|
26
|
-
if current_version >= 3 then
|
27
|
-
s.add_runtime_dependency(%q<fastercsv>, [">= 0", "= 1.4"])
|
28
|
-
else
|
29
|
-
s.add_dependency(%q<fastercsv>, [">= 0", "= 1.4"])
|
30
|
-
end
|
31
|
-
else
|
32
|
-
s.add_dependency(%q<fastercsv>, [">= 0", "= 1.4"])
|
33
|
-
end
|
34
|
-
end
|
data/lib/csv_ext/reminder.rb
DELETED
@@ -1,47 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'rubygems'
|
3
|
-
require 'fastercsv'
|
4
|
-
|
5
|
-
class Reminder
|
6
|
-
|
7
|
-
def self.read_csv(filename)
|
8
|
-
File.open(filename) do |file|
|
9
|
-
self.parse_csv(file)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.parse_csv(io)
|
14
|
-
converters = [lambda {|question| question},
|
15
|
-
lambda {|answer| answer},
|
16
|
-
lambda {|due_on| Date.parse(due_on)},
|
17
|
-
lambda {|history| history.scan(/./).collect {|x| x.to_i}}
|
18
|
-
]
|
19
|
-
|
20
|
-
FasterCSV.parse(io, :skip_blanks => true).collect do |list|
|
21
|
-
list = list.zip(converters).collect {|col, converter| converter[col]}
|
22
|
-
self.new(*list)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
############################################################
|
27
|
-
|
28
|
-
def self.write_csv(filename, reminders)
|
29
|
-
File.open(filename, "w") do |file|
|
30
|
-
file.write(generate_csv(reminders))
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.generate_csv(reminders)
|
35
|
-
FasterCSV.generate do |csv|
|
36
|
-
reminders.each do |reminder|
|
37
|
-
if reminder.history.empty?
|
38
|
-
csv << reminder.to_a.first(3)
|
39
|
-
else
|
40
|
-
csv << reminder.to_a
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
end
|
47
|
-
|
data/test/test_reminder.rb
DELETED
@@ -1,99 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'test/unit'
|
3
|
-
require "#{File.dirname(__FILE__)}/../lib/forgetful"
|
4
|
-
|
5
|
-
class TestReminder < Test::Unit::TestCase
|
6
|
-
|
7
|
-
def assert_raise_message(error, re)
|
8
|
-
exception = assert_raise(error) do
|
9
|
-
yield
|
10
|
-
end
|
11
|
-
assert_match re, exception.message
|
12
|
-
end
|
13
|
-
|
14
|
-
#-------------------------------------------------
|
15
|
-
|
16
|
-
def setup
|
17
|
-
@carbon_question = Reminder.new 'carbon', '6'
|
18
|
-
end
|
19
|
-
|
20
|
-
#-------------------------------------------------
|
21
|
-
|
22
|
-
def test_empty
|
23
|
-
assert_raise_message ArgumentError, /wrong number of arguments \(0 for 2\)/ do
|
24
|
-
Reminder.new
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def test_just_question
|
29
|
-
assert_raise_message ArgumentError, /wrong number of arguments \(1 for 2\)/ do
|
30
|
-
Reminder.new 'carbon'
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
#-------------------------------------------------
|
35
|
-
|
36
|
-
def test_minimum
|
37
|
-
assert_equal 'carbon', @carbon_question.question
|
38
|
-
assert_equal '6', @carbon_question.answer
|
39
|
-
assert_equal Date.today, @carbon_question.due_on
|
40
|
-
assert_equal [], @carbon_question.history
|
41
|
-
end
|
42
|
-
|
43
|
-
def test_minium_array
|
44
|
-
assert_equal ['carbon', '6', Date.today, []], @carbon_question.to_a
|
45
|
-
end
|
46
|
-
|
47
|
-
#-------------------------------------------------
|
48
|
-
|
49
|
-
def test_history_cannot_be_changed
|
50
|
-
history = [5,4,2]
|
51
|
-
|
52
|
-
reminder = Reminder.new('carbon','6',Date.today, history)
|
53
|
-
|
54
|
-
assert_equal [5,4,2], reminder.history
|
55
|
-
|
56
|
-
assert_raise_message TypeError, /can't modify frozen array/ do
|
57
|
-
@carbon_question.history.push(5)
|
58
|
-
end
|
59
|
-
|
60
|
-
history.push(5)
|
61
|
-
|
62
|
-
assert_equal [], @carbon_question.history
|
63
|
-
end
|
64
|
-
|
65
|
-
#-------------------------------------------------
|
66
|
-
|
67
|
-
def test_next_clamped
|
68
|
-
assert_equal ['carbon', '6', Date.today+1, [1]], @carbon_question.next(1).to_a
|
69
|
-
assert_equal ['carbon', '6', Date.today+1, [4]], @carbon_question.next(4).to_a
|
70
|
-
end
|
71
|
-
|
72
|
-
def test_next_unclamped
|
73
|
-
reminder = Reminder.new('carbon', '6', Date.today, [5,5])
|
74
|
-
|
75
|
-
assert_equal ['carbon', '6', Date.today+1, [5,5,1]], reminder.next(1).to_a
|
76
|
-
assert_equal ['carbon', '6', Date.today+16, [5,5,4]], reminder.next(4).to_a
|
77
|
-
end
|
78
|
-
|
79
|
-
#-------------------------------------------------
|
80
|
-
|
81
|
-
def test_review
|
82
|
-
assert_equal true, @carbon_question.review?
|
83
|
-
assert_equal true, @carbon_question.next(0).review?
|
84
|
-
assert_equal true, @carbon_question.next(1).review?
|
85
|
-
assert_equal true, @carbon_question.next(2).review?
|
86
|
-
assert_equal true, @carbon_question.next(3).review?
|
87
|
-
assert_equal false, @carbon_question.next(4).review?
|
88
|
-
assert_equal false, @carbon_question.next(5).review?
|
89
|
-
|
90
|
-
assert_equal true, @carbon_question.next(5).next(0).review?
|
91
|
-
assert_equal true, @carbon_question.next(5).next(1).review?
|
92
|
-
assert_equal true, @carbon_question.next(5).next(2).review?
|
93
|
-
assert_equal true, @carbon_question.next(5).next(3).review?
|
94
|
-
assert_equal false, @carbon_question.next(5).next(4).review?
|
95
|
-
assert_equal false, @carbon_question.next(5).next(5).review?
|
96
|
-
end
|
97
|
-
|
98
|
-
end
|
99
|
-
|
data/test/test_reminder_csv.rb
DELETED
@@ -1,97 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'test/unit'
|
3
|
-
require "#{File.dirname(__FILE__)}/../lib/forgetful"
|
4
|
-
|
5
|
-
class TestReminderCSV < Test::Unit::TestCase
|
6
|
-
|
7
|
-
def test_parse_csv_bare
|
8
|
-
csv = <<END
|
9
|
-
carbon,6
|
10
|
-
nitrogen,7
|
11
|
-
oxygen,8
|
12
|
-
END
|
13
|
-
|
14
|
-
reminders = Reminder.parse_csv(csv)
|
15
|
-
|
16
|
-
assert_equal 3, reminders.length
|
17
|
-
|
18
|
-
assert_equal ['carbon', '6',Date.today, []], reminders[0].to_a
|
19
|
-
assert_equal ['nitrogen','7',Date.today, []], reminders[1].to_a
|
20
|
-
assert_equal ['oxygen', '8',Date.today, []], reminders[2].to_a
|
21
|
-
end
|
22
|
-
|
23
|
-
def test_parse_csv_with_dates
|
24
|
-
csv = <<END
|
25
|
-
carbon,6,2008-11-11
|
26
|
-
nitrogen,7,2008-11-11
|
27
|
-
oxygen,8,2008-11-11
|
28
|
-
END
|
29
|
-
|
30
|
-
reminders = Reminder.parse_csv(csv)
|
31
|
-
|
32
|
-
assert_equal 3, reminders.length
|
33
|
-
|
34
|
-
assert_equal ['carbon', '6',Date.parse('2008-11-11'), []], reminders[0].to_a
|
35
|
-
assert_equal ['nitrogen','7',Date.parse('2008-11-11'), []], reminders[1].to_a
|
36
|
-
assert_equal ['oxygen', '8',Date.parse('2008-11-11'), []], reminders[2].to_a
|
37
|
-
end
|
38
|
-
|
39
|
-
def test_parse_csv_full
|
40
|
-
csv = <<END
|
41
|
-
carbon,6,2008-11-11,5
|
42
|
-
nitrogen,7,2008-11-11,535
|
43
|
-
oxygen,8,2008-11-11,5042
|
44
|
-
END
|
45
|
-
|
46
|
-
reminders = Reminder.parse_csv(csv)
|
47
|
-
|
48
|
-
assert_equal 3, reminders.length
|
49
|
-
|
50
|
-
assert_equal ['carbon', '6',Date.parse('2008-11-11'), [5]], reminders[0].to_a
|
51
|
-
assert_equal ['nitrogen','7',Date.parse('2008-11-11'), [5,3,5]], reminders[1].to_a
|
52
|
-
assert_equal ['oxygen', '8',Date.parse('2008-11-11'), [5,0,4,2]], reminders[2].to_a
|
53
|
-
end
|
54
|
-
|
55
|
-
def test_parse_csv_full_with_spaces
|
56
|
-
csv = <<END
|
57
|
-
|
58
|
-
carbon,6,2008-11-11,035
|
59
|
-
|
60
|
-
|
61
|
-
nitrogen,7,2008-11-11,55555
|
62
|
-
oxygen,8,2008-11-11,1
|
63
|
-
|
64
|
-
END
|
65
|
-
|
66
|
-
reminders = Reminder.parse_csv(csv)
|
67
|
-
|
68
|
-
assert_equal 3, reminders.length
|
69
|
-
|
70
|
-
assert_equal ['carbon', '6',Date.parse('2008-11-11'), [0,3,5]], reminders[0].to_a
|
71
|
-
assert_equal ['nitrogen','7',Date.parse('2008-11-11'), [5,5,5,5,5]], reminders[1].to_a
|
72
|
-
assert_equal ['oxygen', '8',Date.parse('2008-11-11'), [1]], reminders[2].to_a
|
73
|
-
end
|
74
|
-
|
75
|
-
############################################################
|
76
|
-
|
77
|
-
def test_generate_csv
|
78
|
-
reminders = []
|
79
|
-
reminders << Reminder.new('carbon','6')
|
80
|
-
reminders << Reminder.new('nitrogen','7').next(5).next(5).next(5)
|
81
|
-
reminders << Reminder.new('oxygen','8').next(1).next(1).next(1)
|
82
|
-
reminders << Reminder.new('fluorine','9').next(1).next(5).next(1).next(5)
|
83
|
-
reminders << Reminder.new('neon','10',Date.today+5)
|
84
|
-
|
85
|
-
expected =<<END
|
86
|
-
carbon,6,#{Date.today}
|
87
|
-
nitrogen,7,#{(Date.today+16)},555
|
88
|
-
oxygen,8,#{(Date.today+1)},111
|
89
|
-
fluorine,9,#{(Date.today+1)},1515
|
90
|
-
neon,10,#{(Date.today+5)}
|
91
|
-
END
|
92
|
-
|
93
|
-
assert_equal expected, Reminder.generate_csv(reminders)
|
94
|
-
end
|
95
|
-
|
96
|
-
end
|
97
|
-
|
data/test/test_supermemo.rb
DELETED
@@ -1,85 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'test/unit'
|
3
|
-
require "#{File.dirname(__FILE__)}/../lib/forgetful"
|
4
|
-
|
5
|
-
class TestReminder < Test::Unit::TestCase
|
6
|
-
|
7
|
-
def test_next_i
|
8
|
-
qs = [5,4,3,2,1,0]
|
9
|
-
results = [4,4,4,0,0,0]
|
10
|
-
|
11
|
-
qs.zip(results).each do |q,result|
|
12
|
-
assert_equal result, SuperMemo::next_i(q, 3), "next_i for q=#{q} when i=3"
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def test_next_interval_normal
|
17
|
-
qs = [5,4,3,2,1,0]
|
18
|
-
results = [16,16,16,1,1,1]
|
19
|
-
|
20
|
-
qs.zip(results).each do |q,result|
|
21
|
-
assert_equal result, SuperMemo::next_interval(q, 2.7, 2, 6), "next_interval for q=#{q}"
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def test_next_interval_for_i0
|
26
|
-
qs = [5,4,3,2,1,0]
|
27
|
-
results = [1,1,1,1,1,1]
|
28
|
-
|
29
|
-
qs.zip(results).each do |q,result|
|
30
|
-
assert_equal result, SuperMemo::next_interval(q, 2.5, 0, 0), "next_interval for q=#{q}"
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def test_next_interval_for_i1
|
35
|
-
qs = [5,4,3,2,1,0]
|
36
|
-
results = [6,6,6,1,1,1]
|
37
|
-
|
38
|
-
qs.zip(results).each do |q,result|
|
39
|
-
assert_equal result, SuperMemo::next_interval(q, 2.5, 1, 1), "next_interval for q=#{q}"
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def test_next_interval_for_changing_efs
|
44
|
-
efs = [1.3, 2.0, 2.5, 2.6, 2.7]
|
45
|
-
results = [8,12,15,16,16]
|
46
|
-
|
47
|
-
efs.zip(results).each do |ef,result|
|
48
|
-
assert_equal result, SuperMemo::next_interval(5, ef, 2, 6), "next_interval for ef=#{ef}"
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def test_next_ef_normal
|
53
|
-
qs = [5,4,3,2,1,0]
|
54
|
-
results = [2.6,2.5,2.36,2.18,1.96,1.7]
|
55
|
-
|
56
|
-
qs.zip(results).each do |q,result|
|
57
|
-
assert_in_delta result, SuperMemo::next_ef(q, 2.5), 0.00001, "next_ef for q=#{q}"
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def test_next_ef_minimum
|
62
|
-
qs = [5,4,3,2,1,0]
|
63
|
-
results = [1.4,1.3,1.3,1.3,1.3,1.3]
|
64
|
-
|
65
|
-
qs.zip(results).each do |q,result|
|
66
|
-
assert_in_delta result, SuperMemo::next_ef(q, 1.3), 0.00001, "next_ef for q=#{q}"
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
#-------------------------------------------------
|
71
|
-
|
72
|
-
def assert_equal_inspect(expected, actual)
|
73
|
-
assert_equal expected.inspect, actual.inspect
|
74
|
-
end
|
75
|
-
|
76
|
-
def test_traverse
|
77
|
-
assert_equal_inspect [2.5, 0, 0], SuperMemo::traverse([])
|
78
|
-
assert_equal_inspect [2.6, 1, 1], SuperMemo::traverse([5])
|
79
|
-
assert_equal_inspect [2.7, 2, 6], SuperMemo::traverse([5,5])
|
80
|
-
assert_equal_inspect [2.8, 3, 16], SuperMemo::traverse([5,5,5])
|
81
|
-
assert_equal_inspect [2.9, 4, 45], SuperMemo::traverse([5,5,5,5])
|
82
|
-
end
|
83
|
-
|
84
|
-
end
|
85
|
-
|