compsci 0.0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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