compsci 0.1.1.1 → 0.2.0.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.
- 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
|