compsci 0.1.1.1 → 0.2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +74 -5
- data/Rakefile +20 -9
- data/VERSION +1 -1
- data/compsci.gemspec +1 -0
- data/examples/binary_search_tree.rb +16 -0
- data/examples/heap.rb +0 -42
- data/examples/heap_push.rb +46 -0
- data/examples/tree.rb +2 -1
- data/examples/{binary_tree.rb → tree_push.rb} +3 -2
- data/lib/compsci/binary_search_tree.rb +86 -0
- data/lib/compsci/fibonacci.rb +1 -9
- data/lib/compsci/fit.rb +34 -14
- data/lib/compsci/names.rb +3 -4
- data/lib/compsci/node.rb +66 -19
- data/lib/compsci/simplex.rb +173 -0
- data/lib/compsci/simplex/parse.rb +125 -0
- data/lib/compsci/tree.rb +14 -1
- data/test/bench/complete_tree.rb +59 -0
- data/test/bench/fibonacci.rb +0 -4
- data/test/bench/simplex.rb +141 -0
- data/test/bench/tree.rb +20 -15
- data/test/binary_search_tree.rb +106 -0
- data/test/fit.rb +5 -11
- data/test/node.rb +55 -4
- data/test/simplex.rb +291 -0
- data/test/simplex_parse.rb +94 -0
- data/test/tree.rb +33 -9
- metadata +27 -3
data/lib/compsci/tree.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
require 'compsci/node'
|
1
|
+
# require 'compsci/node'
|
2
2
|
|
3
3
|
module CompSci
|
4
|
+
# for now at least, this assumes children simply stack up
|
5
|
+
# children like: [nil, child1, child2] are not supported
|
4
6
|
class Tree
|
5
7
|
attr_reader :root
|
6
8
|
|
@@ -8,6 +10,7 @@ module CompSci
|
|
8
10
|
@root = node_class.new val
|
9
11
|
end
|
10
12
|
|
13
|
+
# does not support children gaps
|
11
14
|
def df_search(node: nil, &blk)
|
12
15
|
node ||= @root
|
13
16
|
return node if yield node
|
@@ -18,6 +21,7 @@ module CompSci
|
|
18
21
|
nil
|
19
22
|
end
|
20
23
|
|
24
|
+
# does not support children gaps
|
21
25
|
def bf_search(node: nil, &blk)
|
22
26
|
node ||= @root
|
23
27
|
destinations = [node]
|
@@ -38,6 +42,15 @@ module CompSci
|
|
38
42
|
end
|
39
43
|
|
40
44
|
class NaryTree < Tree
|
45
|
+
# thanks apeiros
|
46
|
+
# https://gist.github.com/rickhull/d0b579aa08c85430b0dc82a791ff12d6
|
47
|
+
def self.power_of?(num, base)
|
48
|
+
return false if base <= 1
|
49
|
+
mod = 0
|
50
|
+
num, mod = num.divmod(base) until num == 1 || mod > 0
|
51
|
+
mod == 0
|
52
|
+
end
|
53
|
+
|
41
54
|
attr_reader :child_slots
|
42
55
|
|
43
56
|
def initialize(node_class, val, child_slots:)
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'compsci/complete_tree'
|
2
|
+
require 'compsci/timer'
|
3
|
+
require 'compsci/fit'
|
4
|
+
|
5
|
+
include CompSci
|
6
|
+
|
7
|
+
timing = {}
|
8
|
+
ns = [10, 100, 1000, 10_000, 100_000]
|
9
|
+
|
10
|
+
# Note, CompleteTree is a very thin wrapper around Array, so we are just
|
11
|
+
# testing ruby's inherent Array performance here.
|
12
|
+
# Append / push / insert is constant for ruby Arrays.
|
13
|
+
|
14
|
+
puts <<EOF
|
15
|
+
|
16
|
+
#
|
17
|
+
# timing CompleteTree(N)#push where N is the size of the tree
|
18
|
+
#
|
19
|
+
|
20
|
+
EOF
|
21
|
+
|
22
|
+
ns.each { |n|
|
23
|
+
h = CompleteBinaryTree.new
|
24
|
+
n.times { h.push rand }
|
25
|
+
_val, secs = Timer.loop_avg {
|
26
|
+
h.push rand
|
27
|
+
}
|
28
|
+
puts "CompleteTree(%i) push: %0.8f" % [n, secs]
|
29
|
+
timing[n] = secs
|
30
|
+
break if secs > 1
|
31
|
+
}
|
32
|
+
|
33
|
+
a, b, r2, fn = Fit.best timing.keys, timing.values
|
34
|
+
|
35
|
+
puts "best fit: #{fn} (%0.3f); a = %0.6f, b = %0.6f" % [r2, a, b]
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
puts <<EOF
|
40
|
+
|
41
|
+
#
|
42
|
+
# timing CompleteTree#push where N is the count of pushes
|
43
|
+
#
|
44
|
+
|
45
|
+
EOF
|
46
|
+
|
47
|
+
ns.each { |n|
|
48
|
+
h = CompleteBinaryTree.new
|
49
|
+
_val, secs = Timer.loop_avg {
|
50
|
+
n.times { h.push rand }
|
51
|
+
}
|
52
|
+
puts "%ix CompleteTree#push: %0.8f" % [n, secs]
|
53
|
+
timing[n] = secs
|
54
|
+
break if secs > 1
|
55
|
+
}
|
56
|
+
|
57
|
+
a, b, r2, fn = Fit.best timing.keys, timing.values
|
58
|
+
|
59
|
+
puts "best fit: #{fn} (%0.3f); a = %0.6f, b = %0.6f" % [r2, a, b]
|
data/test/bench/fibonacci.rb
CHANGED
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'compsci/simplex'
|
2
|
+
|
3
|
+
include CompSci
|
4
|
+
|
5
|
+
BENCH_IPS = true
|
6
|
+
BENCH_OBJECT_SPACE = true
|
7
|
+
|
8
|
+
SIMPLEX_PARAMS = [
|
9
|
+
# 1 (index 0)
|
10
|
+
[[1, 1],
|
11
|
+
[[2, 1],
|
12
|
+
[1, 2]],
|
13
|
+
[4, 3]],
|
14
|
+
|
15
|
+
[[3, 4],
|
16
|
+
[[1, 1],
|
17
|
+
[2, 1]],
|
18
|
+
[4, 5]],
|
19
|
+
|
20
|
+
[[2, -1],
|
21
|
+
[[1, 2],
|
22
|
+
[3, 2],],
|
23
|
+
[6, 12]],
|
24
|
+
|
25
|
+
[[60, 90, 300],
|
26
|
+
[[1, 1, 1],
|
27
|
+
[1, 3, 0],
|
28
|
+
[2, 0, 1]],
|
29
|
+
[600, 600, 900]],
|
30
|
+
|
31
|
+
# 5
|
32
|
+
[[70, 210, 140],
|
33
|
+
[[1, 1, 1],
|
34
|
+
[5, 4, 4],
|
35
|
+
[40, 20, 30]],
|
36
|
+
[100, 480, 3200]],
|
37
|
+
|
38
|
+
[[2, -1, 2],
|
39
|
+
[[2, 1, 0],
|
40
|
+
[1, 2, -2],
|
41
|
+
[0, 1, 2]],
|
42
|
+
[10, 20, 5]],
|
43
|
+
|
44
|
+
[[11, 16, 15],
|
45
|
+
[[1, 2, Rational(3, 2)],
|
46
|
+
[Rational(2, 3), Rational(2, 3), 1],
|
47
|
+
[Rational(1, 2), Rational(1, 3), Rational(1, 2)]],
|
48
|
+
[12_000, 4_600, 2_400]],
|
49
|
+
|
50
|
+
[[5, 4, 3],
|
51
|
+
[[2, 3, 1],
|
52
|
+
[4, 1, 2],
|
53
|
+
[3, 4, 2]],
|
54
|
+
[5, 11, 8]],
|
55
|
+
|
56
|
+
[[3, 2, -4],
|
57
|
+
[[1, 4, 0],
|
58
|
+
[2, 4,-2],
|
59
|
+
[1, 1,-2]],
|
60
|
+
[5, 6, 2]],
|
61
|
+
|
62
|
+
# 10
|
63
|
+
[[2, -1, 8],
|
64
|
+
[[2, -4, 6],
|
65
|
+
[-1, 3, 4],
|
66
|
+
[0, 0, 2]],
|
67
|
+
[3, 2, 1]],
|
68
|
+
|
69
|
+
[[100_000, 40_000, 18_000],
|
70
|
+
[[20, 6, 3],
|
71
|
+
[0, 1, 0],
|
72
|
+
[-1,-1, 1],
|
73
|
+
[-9, 1, 1]],
|
74
|
+
[182, 10, 0, 0]],
|
75
|
+
|
76
|
+
[[1, 2, 1, 2],
|
77
|
+
[[1, 0, 1, 0],
|
78
|
+
[0, 1, 0, 1],
|
79
|
+
[1, 1, 0, 0],
|
80
|
+
[0, 0, 1, 1]],
|
81
|
+
[1, 4, 2, 2]],
|
82
|
+
|
83
|
+
[[10, -57, -9, -24],
|
84
|
+
[[0.5, -5.5, -2.5, 9],
|
85
|
+
[0.5, -1.5, -0.5, 1],
|
86
|
+
[ 1, 0, 0, 0]],
|
87
|
+
[0, 0, 1]],
|
88
|
+
|
89
|
+
# 14 (index 13)
|
90
|
+
[[25, 20],
|
91
|
+
[[20, 12],
|
92
|
+
[1, 1]],
|
93
|
+
[1800, 8*15]],
|
94
|
+
]
|
95
|
+
|
96
|
+
def new_simplices
|
97
|
+
SIMPLEX_PARAMS.map { |c, a, b| Simplex.new(c, a, b) }
|
98
|
+
end
|
99
|
+
|
100
|
+
if BENCH_IPS
|
101
|
+
require 'benchmark/ips'
|
102
|
+
|
103
|
+
Benchmark.ips do |b|
|
104
|
+
b.config time: 3, warmup: 0.5
|
105
|
+
|
106
|
+
b.report("Simplex init") {
|
107
|
+
new_simplices
|
108
|
+
}
|
109
|
+
|
110
|
+
b.report("init, solve") {
|
111
|
+
new_simplices.each { |s| s.solution }
|
112
|
+
}
|
113
|
+
|
114
|
+
#b.report("Simplex Matrix") {
|
115
|
+
#}
|
116
|
+
|
117
|
+
b.compare!
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
if BENCH_OBJECT_SPACE
|
122
|
+
require 'compsci/timer'
|
123
|
+
require 'objspace'
|
124
|
+
|
125
|
+
def disp_memsize(var, label = '')
|
126
|
+
"memsize(%s): %i" % [label, ObjectSpace.memsize_of(var)]
|
127
|
+
end
|
128
|
+
|
129
|
+
simplices = SIMPLEX_PARAMS.map { |c, a, b|
|
130
|
+
Simplex.new(c, a, b)
|
131
|
+
}
|
132
|
+
|
133
|
+
puts "SIMPLEX_PARAMS.size = #{SIMPLEX_PARAMS.size}"
|
134
|
+
puts "simplices.size = #{simplices.size}"
|
135
|
+
|
136
|
+
puts disp_memsize SIMPLEX_PARAMS, 'SIMPLEX_PARAMS'
|
137
|
+
puts disp_memsize simplices, 'simplices'
|
138
|
+
results = simplices.map { |s| s.solution }
|
139
|
+
puts disp_memsize simplices, 'simplices after solving'
|
140
|
+
puts disp_memsize results, 'results'
|
141
|
+
end
|
data/test/bench/tree.rb
CHANGED
@@ -1,26 +1,31 @@
|
|
1
|
+
require 'compsci/node'
|
1
2
|
require 'compsci/tree'
|
2
|
-
require '
|
3
|
-
require 'minitest/benchmark'
|
3
|
+
require 'benchmark/ips'
|
4
4
|
|
5
5
|
include CompSci
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
7
|
+
Benchmark.ips do |b|
|
8
|
+
b.config time: 3, warmup: 0.5
|
9
|
+
|
10
|
+
b.report("99x BinaryTree(ChildNode)#push") do
|
11
|
+
tree = BinaryTree.new(ChildFlexNode, 42)
|
12
|
+
99.times { tree.push rand 99 }
|
13
13
|
end
|
14
14
|
|
15
|
-
|
16
|
-
tree =
|
17
|
-
|
15
|
+
b.report("99x BinaryTree(FlexNode)#push") do
|
16
|
+
tree = BinaryTree.new(FlexNode, 42)
|
17
|
+
99.times { tree.push rand 99 }
|
18
18
|
end
|
19
19
|
|
20
|
-
|
21
|
-
|
20
|
+
b.report("99x TernaryTree(ChildFlexNode)#push") do
|
21
|
+
tree = TernaryTree.new(ChildFlexNode, 42)
|
22
|
+
99.times { tree.push rand 99 }
|
23
|
+
end
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
+
b.report("99x TernaryTree(FlexNode)#push") do
|
26
|
+
tree = TernaryTree.new(FlexNode, 42)
|
27
|
+
99.times { tree.push rand 99 }
|
25
28
|
end
|
29
|
+
|
30
|
+
b.compare!
|
26
31
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'compsci/node'
|
2
|
+
require 'compsci/binary_search_tree'
|
3
|
+
require 'compsci/names'
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
6
|
+
include CompSci
|
7
|
+
|
8
|
+
describe BinarySearchTree do
|
9
|
+
def ww2_names(count)
|
10
|
+
Array.new(count) { |i| Names::WW2[i].to_s }
|
11
|
+
end
|
12
|
+
|
13
|
+
def nato_names(count)
|
14
|
+
Array.new(count) { |i| Names::NATO[i].to_s }
|
15
|
+
end
|
16
|
+
|
17
|
+
before do
|
18
|
+
@keys = ww2_names(4)
|
19
|
+
@values = Array.new(4) { Names::SOLAR.sample }
|
20
|
+
@nodes = Array.new(4) { |i|
|
21
|
+
KeyNode.new(@values[i], key: @keys[i], children: 2)
|
22
|
+
}
|
23
|
+
@tree = BinarySearchTree.new(@nodes.first)
|
24
|
+
|
25
|
+
# tree will look like:
|
26
|
+
# A:val1
|
27
|
+
# B:val2
|
28
|
+
# C:val3
|
29
|
+
# D:val4
|
30
|
+
end
|
31
|
+
|
32
|
+
it "must display_level" do
|
33
|
+
str = BinarySearchTree.display_level nodes: @nodes, width: 80
|
34
|
+
str.size.must_be :>=, 80 # it can overflow
|
35
|
+
|
36
|
+
str = BinarySearchTree.display_level nodes: @nodes, width: 200
|
37
|
+
str.size.must_equal 200 # it won't overflow
|
38
|
+
|
39
|
+
@keys.each { |k| str.must_include k.to_s }
|
40
|
+
@values.each { |v| str.must_include v.to_s }
|
41
|
+
end
|
42
|
+
|
43
|
+
it "must provide a new_node" do
|
44
|
+
node = BinarySearchTree.new_node('the key', 'the value')
|
45
|
+
node.must_be_kind_of Node
|
46
|
+
node.must_be_kind_of KeyNode
|
47
|
+
node.key.must_equal 'the key'
|
48
|
+
node.value.must_equal 'the value'
|
49
|
+
end
|
50
|
+
|
51
|
+
it "must instantiate with key and value" do
|
52
|
+
tree = BinarySearchTree.new_with_kv('the key', 'the value')
|
53
|
+
node = tree.root
|
54
|
+
node.must_be_kind_of Node
|
55
|
+
node.must_be_kind_of KeyNode
|
56
|
+
node.key.must_equal 'the key'
|
57
|
+
node.value.must_equal 'the value'
|
58
|
+
end
|
59
|
+
|
60
|
+
it "must decide what to do with duplicate nodes" do
|
61
|
+
end
|
62
|
+
|
63
|
+
it "must decide what to do with duplicate keys" do
|
64
|
+
end
|
65
|
+
|
66
|
+
it "must insert nodes" do
|
67
|
+
1.upto(@nodes.size - 1) { |i| @tree[@keys[i]] = @values[i] }
|
68
|
+
@tree.root.children.wont_be_empty
|
69
|
+
@tree.root.children[0].nil?.must_equal true
|
70
|
+
@tree.root.children[1].key.must_equal @keys[1]
|
71
|
+
@tree.root.children[1].children[0].nil?.must_equal true
|
72
|
+
@tree.root.children[1].children[1].value.must_equal @values[2]
|
73
|
+
end
|
74
|
+
|
75
|
+
it "must search nodes" do
|
76
|
+
tree = nil
|
77
|
+
new_order = (0..9).to_a.shuffle
|
78
|
+
new_order.each { |i|
|
79
|
+
k, v = Names::NATO[i], Names::SOLAR.sample
|
80
|
+
if tree.nil?
|
81
|
+
tree = BinarySearchTree.new_with_kv(k, v)
|
82
|
+
else
|
83
|
+
tree[k] = v
|
84
|
+
end
|
85
|
+
}
|
86
|
+
|
87
|
+
2.times {
|
88
|
+
i = new_order.sample
|
89
|
+
key = Names::NATO[new_order.sample]
|
90
|
+
node = tree.search_iterative key
|
91
|
+
node.wont_be_nil
|
92
|
+
node.key.must_equal key
|
93
|
+
}
|
94
|
+
|
95
|
+
2.times {
|
96
|
+
i = new_order.sample
|
97
|
+
key = Names::NATO[new_order.sample]
|
98
|
+
node = tree.search_recursive key
|
99
|
+
node.wont_be_nil
|
100
|
+
node.key.must_equal key
|
101
|
+
}
|
102
|
+
|
103
|
+
tree.search_iterative(Names::SOLAR.sample).must_be_nil
|
104
|
+
tree.search_recursive(Names::SOLAR.sample).must_be_nil
|
105
|
+
end
|
106
|
+
end
|
data/test/fit.rb
CHANGED
@@ -27,24 +27,18 @@ describe Fit do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
# y = a
|
30
|
+
# Note: Thinking about dropping this.
|
31
|
+
# I don't know how to test the variance for constantness or any
|
32
|
+
# alternate measure. A low slope and r2 for linear fit, maybe.
|
33
|
+
#
|
30
34
|
describe "Fit.constant" do
|
31
|
-
it "must
|
35
|
+
it "must stuff" do
|
32
36
|
[0, 1, 10, 100, 1000, 9999].each { |a|
|
33
37
|
y_bar, variance = Fit.constant(@xs, @xs.map { |x| a })
|
34
38
|
y_bar.must_equal a
|
35
39
|
variance.must_equal 0
|
36
40
|
}
|
37
41
|
end
|
38
|
-
|
39
|
-
# note, this test can possibly fail depending on the uniformity of
|
40
|
-
# rand's output for our sample
|
41
|
-
it "must accept noisy constant data" do
|
42
|
-
[0, 1, 10, 100, 1000, 9999].each { |a|
|
43
|
-
y_bar, variance = Fit.constant(@xs, @xs.map { |x| a + noise() })
|
44
|
-
y_bar.must_be_close_to a, 0.3
|
45
|
-
(variance / @xs.size).must_be_close_to 0.1, 0.09
|
46
|
-
}
|
47
|
-
end
|
48
42
|
end
|
49
43
|
|
50
44
|
# y = a + b*ln(x)
|
data/test/node.rb
CHANGED
@@ -16,6 +16,42 @@ describe Node do
|
|
16
16
|
}
|
17
17
|
end
|
18
18
|
|
19
|
+
it "must not respond to :parent" do
|
20
|
+
@martin_sheen.respond_to?(:parent).must_equal false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe KeyNode do
|
25
|
+
before do
|
26
|
+
@martin_sheen = KeyNode.new 'martin', key: 'marty'
|
27
|
+
@charlie_sheen = KeyNode.new 'charlie', key: 'charles'
|
28
|
+
@emilio_estevez = KeyNode.new 'emilio', key: 'emile'
|
29
|
+
end
|
30
|
+
|
31
|
+
it "must start with no children" do
|
32
|
+
[@martin_sheen, @charlie_sheen, @emilio_estevez].each { |n|
|
33
|
+
n.children.must_be_empty
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
it "must not respond to :parent" do
|
38
|
+
@martin_sheen.respond_to?(:parent).must_equal false
|
39
|
+
end
|
40
|
+
|
41
|
+
it "must have a key" do
|
42
|
+
@martin_sheen.key.must_equal 'marty'
|
43
|
+
@charlie_sheen.key.must_equal 'charles'
|
44
|
+
@emilio_estevez.key.must_equal 'emile'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe FlexNode do
|
49
|
+
before do
|
50
|
+
@martin_sheen = FlexNode.new 'martin'
|
51
|
+
@charlie_sheen = FlexNode.new 'charlie'
|
52
|
+
@emilio_estevez = FlexNode.new 'emilio'
|
53
|
+
end
|
54
|
+
|
19
55
|
it "must track children" do
|
20
56
|
@charlie_sheen.add_parent(@martin_sheen)
|
21
57
|
@martin_sheen.children.must_include @charlie_sheen
|
@@ -25,10 +61,6 @@ describe Node do
|
|
25
61
|
@martin_sheen.children.must_include @emilio_estevez
|
26
62
|
end
|
27
63
|
|
28
|
-
it "must not respond to :parent" do
|
29
|
-
@martin_sheen.respond_to?(:parent).must_equal false
|
30
|
-
end
|
31
|
-
|
32
64
|
it "must create children from scalars" do
|
33
65
|
@martin_sheen.new_child 'fake_emilio'
|
34
66
|
@martin_sheen.children.size.must_equal 1
|
@@ -51,6 +83,25 @@ describe ChildNode do
|
|
51
83
|
}
|
52
84
|
end
|
53
85
|
|
86
|
+
it "must track parent and children" do
|
87
|
+
@charlie_sheen.set_parent(0, @martin_sheen)
|
88
|
+
@charlie_sheen.parent.must_equal @martin_sheen
|
89
|
+
@martin_sheen.children.must_include @charlie_sheen
|
90
|
+
|
91
|
+
@martin_sheen.children.wont_include @emilio_estevez
|
92
|
+
@martin_sheen.set_child(0, @emilio_estevez)
|
93
|
+
@martin_sheen.children.must_include @emilio_estevez
|
94
|
+
@emilio_estevez.parent.must_equal @martin_sheen
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe ChildFlexNode do
|
99
|
+
before do
|
100
|
+
@martin_sheen = ChildFlexNode.new 'martin'
|
101
|
+
@charlie_sheen = ChildFlexNode.new 'charlie'
|
102
|
+
@emilio_estevez = ChildFlexNode.new 'emilio'
|
103
|
+
end
|
104
|
+
|
54
105
|
it "must track parent and children" do
|
55
106
|
@charlie_sheen.add_parent(@martin_sheen)
|
56
107
|
@charlie_sheen.parent.must_equal @martin_sheen
|