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.
@@ -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]
@@ -134,10 +134,6 @@ if BENCHMARK_IPS
134
134
  Fibonacci.dynamic(num)
135
135
  }
136
136
 
137
- b.report("Fibonacci.dynamic_fast(#{num})") {
138
- Fibonacci.dynamic_fast(num)
139
- }
140
-
141
137
  b.report("Fibonacci.matrix(#{num})") {
142
138
  Fibonacci.matrix(num)
143
139
  }
@@ -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
@@ -1,26 +1,31 @@
1
+ require 'compsci/node'
1
2
  require 'compsci/tree'
2
- require 'minitest/autorun'
3
- require 'minitest/benchmark'
3
+ require 'benchmark/ips'
4
4
 
5
5
  include CompSci
6
6
 
7
- describe "BinaryTree#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]
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
- bench_performance_constant "BinaryTree#push (constant)" do |n|
16
- tree = NaryTree.new(ChildNode, 42, child_slots: 2)
17
- n.times { tree.push rand 99 }
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
- bench_performance_linear "BinaryTree#push (linear)" do |n|
21
- skip "this fails with r^2 around 0.91"
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
- tree = NaryTree.new(ChildNode, 42, child_slots: 2)
24
- n.times { tree.push rand 99 }
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
@@ -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 accept constant data" do
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)
@@ -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