compsci 0.0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,140 @@
1
+ require 'compsci'
2
+
3
+ module CompSci
4
+ class Tree
5
+ attr_reader :root, :child_slots
6
+
7
+ def initialize(root_node, child_slots: 2)
8
+ @root = root_node
9
+ @child_slots = child_slots
10
+ @open_parent = @root
11
+ end
12
+
13
+ def push(value)
14
+ self.open_parent.new_child value
15
+ end
16
+
17
+ def open_parent?(node)
18
+ node.children.size < @child_slots
19
+ end
20
+
21
+ def open_parent
22
+ return @open_parent if self.open_parent?(@open_parent)
23
+ @open_parent = self.bf_search { |n| self.open_parent?(n) }
24
+ end
25
+
26
+ def df_search(node: nil, &blk)
27
+ node ||= @root
28
+ return node if yield node
29
+ node.children.each { |c|
30
+ stop_node = self.df_search(node: c, &blk)
31
+ return stop_node if stop_node
32
+ }
33
+ nil
34
+ end
35
+
36
+ def df_search_generic(node: nil, &blk)
37
+ # Perform pre-order operation
38
+ # children.each { Perform in-order operation }
39
+ # Perform post-order operation
40
+ puts "not defined yet"
41
+ end
42
+
43
+ def bf_search(node: nil, &blk)
44
+ node ||= @root
45
+ destinations = [node]
46
+ while !destinations.empty?
47
+ node = destinations.shift
48
+ return node if yield node
49
+ destinations += node.children
50
+ end
51
+ nil
52
+ end
53
+
54
+ class Node
55
+ attr_accessor :value, :parent
56
+ attr_reader :children
57
+
58
+ def initialize(value)
59
+ @value = value
60
+ @parent = nil
61
+ @children = []
62
+ # @metadata = {}
63
+ end
64
+
65
+ def add_child(node)
66
+ node.parent ||= self
67
+ raise "node has a parent: #{node.parent}" if node.parent != self
68
+ @children << node
69
+ end
70
+
71
+ def new_child(value)
72
+ self.add_child self.class.new(value)
73
+ end
74
+
75
+ def add_parent(node)
76
+ @parent = node
77
+ node.add_child(self)
78
+ end
79
+
80
+ def to_s
81
+ @value.to_s
82
+ end
83
+
84
+ def inspect
85
+ "#<%s:0x%0xi @value=%s @children=[%s]>" %
86
+ [self.class,
87
+ self.object_id,
88
+ self.to_s,
89
+ @children.map(&:to_s).join(', ')]
90
+ end
91
+ end
92
+ end
93
+
94
+ class BinaryTree < Tree
95
+ def initialize(root_node)
96
+ super(root_node, child_slots: 2)
97
+ end
98
+
99
+ def bf_print(node: nil, width: 80)
100
+ count = 0
101
+ self.bf_search(node: node) { |n|
102
+ count += 1
103
+ level = Math.log(count, 2).floor
104
+ block_width = width / (2**level)
105
+ puts if 2**level == count and count > 1
106
+ print n.to_s.ljust(block_width / 2, ' ').rjust(block_width, ' ')
107
+ }
108
+ puts
109
+ end
110
+ end
111
+
112
+ # A CompleteBinaryTree can very efficiently use an array for storage using
113
+ # simple arithmetic to determine parent child relationships.
114
+ #
115
+ class CompleteBinaryTree
116
+ # integer math says idx 2 and idx 1 both have parent at idx 0
117
+ def self.parent_idx(idx)
118
+ (idx-1) / 2
119
+ end
120
+
121
+ def self.children_idx(idx)
122
+ [2*idx + 1, 2*idx + 2]
123
+ end
124
+
125
+ attr_reader :store
126
+
127
+ def initialize(store: [])
128
+ @store = store
129
+ # yield self if block_given?
130
+ end
131
+
132
+ def size
133
+ @store.size
134
+ end
135
+
136
+ def last_idx
137
+ @store.size - 1 unless @store.empty?
138
+ end
139
+ end
140
+ end
data/lib/compsci.rb ADDED
@@ -0,0 +1,2 @@
1
+ module CompSci
2
+ end
data/test/bench/fib.rb ADDED
@@ -0,0 +1,114 @@
1
+ require 'compsci/fib'
2
+ require 'minitest/autorun'
3
+ require 'minitest/benchmark'
4
+
5
+ include CompSci
6
+
7
+ # CLASSIC_RANGE = [3, 5, 10, 15, 20, 25, 30, 31, 32, 33, 34, 35]
8
+ CLASSIC_RANGE = [10, 15, 20, 25, 30, 31, 32, 33, 34, 35]
9
+ # RECURSIVE_RANGE = [10, 100, 1000, 2500, 5000, 7500]
10
+ RECURSIVE_RANGE = [100, 1000, 2500, 5000, 7500]
11
+ # CACHE_RANGE = [100, 1000, 10000, 100000, 112500, 125000]
12
+ CACHE_RANGE = [100, 1000, 10000, 100000]
13
+
14
+ # this causes churn at the process level and impacts other benchmarks
15
+ # DYNAMIC_RANGE = [100, 1000, 10000, 100000, 200000, 500000]
16
+ DYNAMIC_RANGE = [100, 1000, 10000, 100000]
17
+
18
+ #SPEC_BENCHMARK = true
19
+ #CLASS_BENCHMARK = false
20
+ #BENCHMARK_IPS = true
21
+ SPEC_BENCHMARK = false
22
+ CLASS_BENCHMARK = true
23
+ BENCHMARK_IPS = false
24
+
25
+
26
+ if SPEC_BENCHMARK
27
+ describe "Fibonacci.classic Benchmark" do
28
+ bench_range do
29
+ CLASSIC_RANGE
30
+ end
31
+
32
+ fc = ["Fibonacci.classic (exponential, 0.95)", 0.95]
33
+ bench_performance_exponential(*fc) do |n|
34
+ Fibonacci.classic(n)
35
+ end
36
+ end
37
+
38
+ describe "Fibonacci.cache_recursive Benchmark" do
39
+ bench_range do
40
+ RECURSIVE_RANGE
41
+ end
42
+
43
+ fcr = ["Fibonacci.cache_recursive (linear, 0.95)", 0.95]
44
+ bench_performance_linear(*fcr) do |n|
45
+ Fibonacci.cache_recursive(n)
46
+ end
47
+ end
48
+
49
+ describe "Fibonacci.cache_iterative Benchmark" do
50
+ bench_range do
51
+ CACHE_RANGE
52
+ end
53
+
54
+ fci = ["Fibonacci.cache_iterative (linear, 0.99)", 0.99]
55
+ bench_performance_linear(*fci) do |n|
56
+ Fibonacci.cache_iterative(n)
57
+ end
58
+ end
59
+
60
+ describe "Fibonacci.dynamic Benchmark" do
61
+ bench_range do
62
+ DYNAMIC_RANGE
63
+ end
64
+
65
+ fd = ["Fibonacci.dynamic (linear, 0.99)", 0.99]
66
+ bench_performance_linear(*fd) do |n|
67
+ Fibonacci.dynamic(n)
68
+ end
69
+ end
70
+ end
71
+
72
+ if CLASS_BENCHMARK
73
+ require 'compsci/timer'
74
+
75
+ class BenchFib < Minitest::Benchmark
76
+ def bench_fib
77
+ times = CLASSIC_RANGE.map { |n|
78
+ _answer, elapsed = Timer.elapsed { Fibonacci.classic(n) }
79
+ elapsed
80
+ }
81
+ _a, _b, r2 = self.fit_exponential(CLASSIC_RANGE, times)
82
+ puts
83
+ puts "self-timed Fibonacci.classic(n) exponential fit: %0.3f" % r2
84
+ puts
85
+ end
86
+ end
87
+ end
88
+
89
+ if BENCHMARK_IPS
90
+ require 'benchmark/ips'
91
+
92
+ Benchmark.ips do |b|
93
+ b.config time: 3, warmup: 0.5
94
+ num = 25
95
+
96
+ b.report("Fibonacci.classic(#{num})") {
97
+ Fibonacci.classic(num)
98
+ }
99
+
100
+ b.report("Fibonacci.cache_recursive(#{num})") {
101
+ Fibonacci.cache_recursive(num)
102
+ }
103
+
104
+ b.report("Fibonacci.cache_iterative(#{num})") {
105
+ Fibonacci.cache_iterative(num)
106
+ }
107
+
108
+ b.report("Fibonacci.dynamic(#{num})") {
109
+ Fibonacci.dynamic(num)
110
+ }
111
+
112
+ b.compare!
113
+ end
114
+ end
@@ -0,0 +1,15 @@
1
+ require 'compsci/heap'
2
+ require 'minitest/autorun'
3
+ require 'minitest/benchmark'
4
+
5
+ include CompSci
6
+
7
+ describe "Heap#push Benchmark" do
8
+ before do
9
+ @heap = Heap.new
10
+ end
11
+
12
+ bench_performance_constant "Heap#push (constant, 0.9999)", 0.9999 do |n|
13
+ n.times { @heap.push rand 99999 }
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ require 'compsci/tree'
2
+ require 'minitest/autorun'
3
+ require 'minitest/benchmark'
4
+
5
+ include CompSci
6
+
7
+ describe "Tree#push Benchmark" do
8
+ bench_range do
9
+ # note, 5000 takes way too long and is definitely not constant time
10
+ # TODO: BUG?
11
+ # [10, 100, 1000, 2000, 5000]
12
+ [10, 100, 1000, 2000]
13
+ end
14
+
15
+ bench_performance_constant "Tree#push (constant)" do |n|
16
+ tree = Tree.new Tree::Node.new 42
17
+ n.times { tree.push rand 99 }
18
+ end
19
+ end
data/test/fib.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'compsci/fib'
2
+ require 'minitest/autorun'
3
+
4
+ include CompSci
5
+
6
+ describe Fibonacci do
7
+ it "must calculate fib(10) == 55" do
8
+ Fibonacci.classic(10).must_equal 55
9
+ Fibonacci.cache_recursive(10).must_equal 55
10
+ Fibonacci.cache_iterative(10).must_equal 55
11
+ Fibonacci.dynamic(10).must_equal 55
12
+ end
13
+ end
data/test/fit.rb ADDED
@@ -0,0 +1,162 @@
1
+ require 'compsci/fit'
2
+ require 'minitest/autorun'
3
+
4
+ include CompSci
5
+
6
+ describe Fit do
7
+ before do
8
+ @xs = [1, 2, 5, 10, 20, 50, 100, 200, 500]
9
+ end
10
+
11
+ describe "sigma" do
12
+ it "must answer correctly" do
13
+ Fit.sigma([1, 2, 3]).must_equal 6
14
+ Fit.sigma([1, 2, 3]) { |n| n ** 2 }.must_equal 14
15
+ end
16
+ end
17
+
18
+ describe "error" do
19
+ it "must calculate r^2" do
20
+ Fit.error([[1, 1], [2, 2], [3, 3]]) { |x| x }.must_equal 1.0
21
+ Fit.error([[1, 1], [2, 2], [3, 4]]) { |x| x }.must_be_close_to 0.785
22
+ end
23
+ end
24
+
25
+ # y = a
26
+ describe "constant" do
27
+ # note, this test can possibly fail depending on the uniformity of
28
+ # rand's output for our sample
29
+ it "must accept constant data" do
30
+ [0, 1, 10, 100, 1000, 9999].each { |a|
31
+ ys = @xs.map { |x| a + (rand - 0.5) }
32
+ y_bar, variance = Fit.constant(@xs, ys)
33
+ var_val = variance / ys.size
34
+ y_bar.must_be_close_to a, 0.3
35
+ var_val.must_be_close_to 0.1, 0.09
36
+ }
37
+ end
38
+ end
39
+
40
+ # y = a + b*ln(x)
41
+ describe "logarithmic" do
42
+ it "must accept logarithmic data" do
43
+ [-9999, -2000, -500, -0.01, 0.01, 500, 2000, 9999].each { |a|
44
+ [-9999, -2000, -500, -0.01, 0.01, 500, 2000, 9999].each { |b|
45
+ ary = Fit.logarithmic(@xs, @xs.map { |x| a + b * Math.log(x) })
46
+ ary[0].must_be_close_to a
47
+ ary[1].must_be_close_to b
48
+ ary[2].must_equal 1.0
49
+ }
50
+ }
51
+ end
52
+ end
53
+
54
+ # y = a + bx
55
+ describe "linear" do
56
+ it "must accept linear data" do
57
+ [-9999, -2000, -500, -0.01, 0.01, 500, 2000, 9999].each { |a|
58
+ [-9999, -2000, -500, -0.01, 0.01, 500, 2000, 9999].each { |b|
59
+ ary = Fit.linear(@xs, @xs.map { |x| a + b * x })
60
+ ary[0].must_be_close_to a
61
+ ary[1].must_be_close_to b
62
+ ary[2].must_equal 1.0
63
+ }
64
+ }
65
+ end
66
+
67
+ # test that b is near 0; (1 - b) is similar magnitude to r2 in terms of
68
+ # threshold
69
+ # here's the deal: r2 is usually pretty low, but sometimes it is up over
70
+ # 0.5, if rand() is being less than uniform in our sample
71
+ # so, accept a wide range for r2
72
+ # and let's check against 1 - b
73
+ #
74
+ # note, this test can possibly fail depending on the uniformity of
75
+ # rand's output for our sample
76
+ #
77
+ it "must accept constant data" do
78
+ r2s = []
79
+ [0, 1, 10, 100, 1000, 9999].each { |a|
80
+ ys = @xs.map { |x| a + (rand - 0.5) }
81
+ ary = Fit.linear(@xs, ys)
82
+ ary[0].must_be_close_to a, 0.4
83
+ ary[1].must_be_close_to 0, 0.05
84
+ r2s << ary[2]
85
+ }
86
+ mean_r2 = Fit.sigma(r2s) / r2s.size
87
+ mean_r2.must_be_close_to 0.15, 0.15
88
+ end
89
+
90
+ it "must reject nonlinear data" do
91
+ skip "investigate further"
92
+ # this should be quite un-linear; expect r2 below 0.8
93
+ #
94
+ # ACTUALLY
95
+ #
96
+ # the r2 for fit_linear is mostly about the relative fit of a sloped
97
+ # line compared to zero slope (i.e. y_bar)
98
+ #
99
+ # this is why a linear r2 close to 1.0 is the wrong test for fit_constant
100
+ # because the relative fit of the sloped line (slope near 0) doesn't
101
+ # "explain" much relative to y_bar
102
+ #
103
+ # in the case where y = x^3, a linear fit may still have a high r2,
104
+ # because the error for the y_bar predictor is astronomical. A super
105
+ # steep slope fits (relative to the zero slope mean) pretty well.
106
+
107
+ # this calls into question how useful r2 is, as we need it to be a
108
+ # threshold value due to noise, yet even a terrible fit like trying to
109
+ # match x^3 is hard to distinguish from noise
110
+ #
111
+
112
+ a = -50
113
+ b = 1.3
114
+ ys = @xs.map { |x| a + b * x**2 + x**3 }
115
+ ary = Fit.linear(@xs, ys)
116
+ if ary[2] > 0.85
117
+ puts
118
+ puts "fit_linear: #{ary.inspect}"
119
+ puts "y = %0.2f + %0.2f(x) (r2 = %0.3f)" % ary
120
+ puts
121
+ col1, col2 = 5, 15
122
+ puts "x".ljust(col1, ' ') + "y".ljust(col2, ' ') + "predicted"
123
+ puts '---'.ljust(col1, ' ') + '---'.ljust(col2, ' ') + '---'
124
+ @xs.zip(ys).each { |(x,y)|
125
+ puts x.to_s.ljust(col1, ' ') + y.to_s.ljust(col2, ' ') +
126
+ "%0.2f" % (ary[0] + ary[1] * x)
127
+ }
128
+ # ary[2].must_be :<, 0.8
129
+ ary[2].must_be :<, 0.9
130
+ end
131
+ end
132
+ end
133
+
134
+ # y = ae^(bx)
135
+ describe "exponential" do
136
+ it "must accept exponential data" do
137
+ [0.001, 7.5, 500, 1000, 5000, 9999].each { |a|
138
+ [-1.4, -1.1, -0.1, 0.01, 0.5, 0.75].each { |b|
139
+ ary = Fit.exponential(@xs, @xs.map { |x| a * Math::E**(b * x) })
140
+ ary[0].must_be_close_to a
141
+ ary[1].must_be_close_to b
142
+ ary[2].must_equal 1.0
143
+ }
144
+ }
145
+ end
146
+ end
147
+
148
+ # y = ax^b
149
+ describe "power" do
150
+ it "must accept power data" do
151
+ [0.01, 7.5, 500, 1000, 5000, 9999].each { |a|
152
+ [-114, -100, -10, -0.5, -0.1, 0.1, 0.75, 10, 50, 60].each { |b|
153
+ next if b == -114 # Fit.error warning: Bignum out of Float range
154
+ ary = Fit.power(@xs, @xs.map { |x| a * x**b })
155
+ ary[0].must_be_close_to a
156
+ ary[1].must_be_close_to b
157
+ ary[2].must_equal 1.0
158
+ }
159
+ }
160
+ end
161
+ end
162
+ end
data/test/heap.rb ADDED
@@ -0,0 +1,46 @@
1
+ require 'compsci/heap'
2
+ require 'minitest/autorun'
3
+
4
+ include CompSci
5
+
6
+ describe Heap do
7
+ before do
8
+ @maxheap = Heap.new
9
+ @minheap = Heap.new(minheap: true)
10
+ @inserts = (1..10).to_a
11
+ @inserts.each { |i|
12
+ @maxheap.push i
13
+ @minheap.push i
14
+ }
15
+ end
16
+
17
+ it "must satisfy the heap property" do
18
+ @maxheap.heap?.must_equal true
19
+ @minheap.heap?.must_equal true
20
+ @minheap.store.must_equal @inserts
21
+ @maxheap.store.wont_equal @inserts
22
+ @maxheap.store.wont_equal @inserts.reverse
23
+ end
24
+
25
+ it "must recognize heap violations" do
26
+ @minheap.store.push 0
27
+ @minheap.heap?.must_equal false
28
+ @minheap.sift_up @minheap.last_idx
29
+ @minheap.heap?.must_equal true
30
+
31
+ @minheap.store.unshift 10
32
+ @minheap.heap?.must_equal false
33
+ @minheap.sift_down 0
34
+ @minheap.heap?.must_equal true
35
+
36
+ @maxheap.store.push 10
37
+ @maxheap.heap?.must_equal false
38
+ @maxheap.sift_up @maxheap.last_idx
39
+ @maxheap.heap?.must_equal true
40
+
41
+ @maxheap.store.unshift 0
42
+ @maxheap.heap?.must_equal false
43
+ @maxheap.sift_down 0
44
+ @maxheap.heap?.must_equal true
45
+ end
46
+ end
data/test/timer.rb ADDED
@@ -0,0 +1,48 @@
1
+ require 'compsci/timer'
2
+ require 'minitest/autorun'
3
+
4
+ include CompSci
5
+
6
+ describe Timer do
7
+ describe "elapsed" do
8
+ it "must return the block value and positive number" do
9
+ answer, elapsed = Timer.elapsed { sleep 0.01; :foo }
10
+ answer.must_equal :foo
11
+ elapsed.must_be_close_to 0.015, 0.005
12
+ end
13
+ end
14
+
15
+ describe "since" do
16
+ it "must be positive" do
17
+ start = Timer.now
18
+ sleep 0.01
19
+ Timer.since(start).must_be_close_to 0.015, 0.005
20
+ end
21
+ end
22
+
23
+ describe "loop_average" do
24
+ it "return the block value and a positive number" do
25
+ start = Timer.now
26
+ answer, avg_et = Timer.loop_average(seconds: 0.25) {
27
+ sleep 0.01
28
+ :foo
29
+ }
30
+ answer.must_equal :foo
31
+ avg_et.must_be_close_to 0.01, 0.005
32
+ Timer.since(start).must_be_close_to 0.3, 0.05
33
+ end
34
+
35
+ it "must repeat short loops and stop on time" do
36
+ true.must_equal true
37
+ end
38
+
39
+ it "must not interrupt long loops" do
40
+ start = Timer.now
41
+ _answer, avg_et = Timer.loop_average(seconds: 0.1) {
42
+ sleep 0.25
43
+ }
44
+ Timer.since(start).must_be_close_to avg_et, 0.05
45
+ avg_et.must_be_close_to 0.3, 0.05
46
+ end
47
+ end
48
+ end