compsci 0.0.1.1 → 0.0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dc7af3ec0bc93ea91b485b329e66033cca137597
4
- data.tar.gz: f62c24c3171a6534e92fa8f53dba942e009442c3
3
+ metadata.gz: 4ee20715906280e4a366f493195ae4decf079cf1
4
+ data.tar.gz: '0088708c15ca4dae9cdc58ef35d1f85dd60203cc'
5
5
  SHA512:
6
- metadata.gz: fe64178808bef0a929882318ed08941de2179940bc332ee42ca9f48f0430e566ae6299f628aa4ad11573787c59fe9746cd8a23b5309fa1ecd92117fd1c0daf16
7
- data.tar.gz: 0ce9187b7eb0d17add238c6344452e344685e00126927359f2d2d54f6e3fbc8ce5a389d153027c7f626b8039f86e8b7409d1950f2d14afbd5619ec8dd878ff4c
6
+ metadata.gz: 38071308dbc5a6e1988abf8671166c10b793f4baa9bb7371c1fe65c3d2570f1a38caaf61042fa247d58a0451b8711b1ec0eb0ea98e67bfe9fab8bbd8c0652318
7
+ data.tar.gz: 11a04a2484e15b29a049c1d6c48c373e2360c98b24f02d4bc874353aa29917acbf53acc1932b1d3629796afd5956c91048a14ff8c46b54904f457d1588e7aa45
data/README.md CHANGED
@@ -6,36 +6,39 @@ Provided are some toy implementations for some basic computer science problems.
6
6
 
7
7
  ## [`Tree`](/lib/compsci/tree.rb) data structures
8
8
 
9
- * `Tree` - enforces number of children per node
10
- * `Tree::Node` - references parent and children nodes
11
- * `BinaryTree` - subclass of `Tree`; child_slots == 2
9
+ * `Node` - references children nodes only
10
+ * `ChildNode` - references parent and children nodes
11
+ * `Tree` - tracks the `root` node; provides `df_search` and `bf_search`
12
+ * `NaryTree` - enforces number of children per node via `child_slots`
13
+ * `BinaryTree` - `NaryTree` with `child_slots` == 2; provides `to_s`
12
14
  * `CompleteBinaryTree` - efficient Array implementation
13
15
 
14
16
  ## [`Heap`](lib/compsci/heap.rb) data structure
15
17
 
16
18
  Implemented with a `CompleteBinaryTree` for storage using simple arithmetic to
17
19
  determine array indices for parent and children. See the
18
- [heap example](https://github.com/rickhull/compsci/blob/master/eamples/heap.rb)
19
- which can be executed (among other examples) via `rake examples`.
20
+ [heap example](examples/heap.rb) which can be executed (among other examples)
21
+ via `rake examples`.
20
22
 
21
23
  Both minheaps and maxheaps are supported. The primary operations are
22
- `Heap#push` and `Heap#pop`. My basic Vagrant VM gets over 350k pushes per
24
+ `Heap#push` and `Heap#pop`. My basic Vagrant VM gets around 500k pushes per
23
25
  second, constant up past 1M pushes.
24
26
 
25
- ## [`Fibonacci`](lib/compsci/fib.rb) functions
27
+ ## [`Fibonacci`](lib/compsci/fibonacci.rb) functions
26
28
 
27
29
  * `Fibonacci.classic(n)` - naive, recursive
28
30
  * `Fibonacci.cache_recursive(n)` - as above, caching already computed results
29
31
  * `Fibonacci.cache_iterative(n)` - as above but iterative
30
32
  * `Fibonacci.dynamic(n)` - as above but without a cache structure
33
+ * `Fibonacci.matrix(n)` - matrix is magic; beats dynamic around n=500
31
34
 
32
35
  ## [`Timer`](/lib/compsci/timer.rb) functions
33
36
 
34
37
  * `Timer.now` - uses `Process::CLOCK_MONOTONIC` if available
38
+ * `Timer.since` - provides the elapsed time since a prior time
35
39
  * `Timer.elapsed` - provides the elapsed time to run a block
36
40
  * `Timer.loop_average` - runs a block repeatedly and provides the mean elapsed
37
41
  time
38
- * `Timer.since` - provides the elapsed time since a prior time
39
42
 
40
43
  ## [`Fit`](lib/compsci/fit.rb) functions
41
44
 
data/Rakefile CHANGED
@@ -5,14 +5,14 @@ Rake::TestTask.new :test do |t|
5
5
  t.warning = true
6
6
  end
7
7
 
8
- Rake::TestTask.new bench: :test do |t|
8
+ Rake::TestTask.new bench: [:test, :loadavg] do |t|
9
9
  t.pattern = "test/bench/*.rb"
10
10
  t.warning = true
11
11
  t.description = "Run benchmarks"
12
12
  end
13
13
 
14
14
  desc "Run example scripts"
15
- task examples: :test do
15
+ task examples: [:test, :loadavg] do
16
16
  Dir['examples/**/*.rb'].each { |filepath|
17
17
  puts
18
18
  sh "ruby -Ilib #{filepath}"
@@ -31,7 +31,7 @@ metrics_tasks = []
31
31
  begin
32
32
  require 'flog_task'
33
33
  FlogTask.new do |t|
34
- t.threshold = 420
34
+ t.threshold = 450
35
35
  t.dirs = ['lib']
36
36
  t.verbose = true
37
37
  end
@@ -68,7 +68,7 @@ task code_metrics: metrics_tasks
68
68
 
69
69
  desc "Show current system load"
70
70
  task "loadavg" do
71
- puts File.read "/proc/loadavg"
71
+ puts "/proc/loadavg %s" % (File.read("/proc/loadavg") rescue "Unavailable")
72
72
  end
73
73
 
74
74
  def lib_sh(cmd)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1.1
1
+ 0.0.2.1
data/compsci.gemspec CHANGED
@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
16
16
  README.md
17
17
  Rakefile
18
18
  lib/compsci.rb
19
- lib/compsci/fib.rb
19
+ lib/compsci/fibonacci.rb
20
20
  lib/compsci/fit.rb
21
21
  lib/compsci/heap.rb
22
22
  lib/compsci/timer.rb
@@ -24,12 +24,12 @@ Gem::Specification.new do |s|
24
24
  examples/binary_tree.rb
25
25
  examples/heap.rb
26
26
  examples/timer.rb
27
- test/fib.rb
27
+ test/fibonacci.rb
28
28
  test/fit.rb
29
29
  test/heap.rb
30
30
  test/timer.rb
31
31
  test/tree.rb
32
- test/bench/fib.rb
32
+ test/bench/fibonacci.rb
33
33
  test/bench/heap.rb
34
34
  test/bench/tree.rb
35
35
  ]
@@ -1,16 +1,60 @@
1
1
  require 'compsci/tree'
2
+ require 'compsci/timer'
2
3
 
3
4
  include CompSci
4
5
 
6
+ puts <<EOF
7
+ #
8
+ # 3 seconds worth of pushes
9
+ #
10
+
11
+ EOF
12
+
13
+ count = 0
14
+ start = Timer.now
15
+ start_1k = Timer.now
16
+ tree = BinaryTree.new(ChildNode, rand(99))
17
+
18
+ loop {
19
+ count += 1
20
+
21
+ if count % 100 == 0
22
+ _ans, push_elapsed = Timer.elapsed { tree.push rand 99 }
23
+ puts "%ith push: %0.8f s" % [count, push_elapsed]
24
+
25
+ if count % 1000 == 0
26
+ push_1k_elapsed = Timer.since start_1k
27
+ puts "-----------"
28
+ puts " 1k push: %0.4f s (%i push / s)" %
29
+ [push_1k_elapsed, 1000.to_f / push_1k_elapsed]
30
+ puts
31
+ start_1k = Timer.now
32
+ end
33
+ else
34
+ tree.push rand 99
35
+ end
36
+
37
+ break if Timer.since(start) > 3
38
+ }
39
+
40
+ puts "pushed %i items in %0.1f s" % [count, Timer.since(start)]
41
+ puts
42
+
43
+ puts <<EOF
44
+ #
45
+ # 30 inserts, puts, df_search
46
+ #
47
+
48
+ EOF
49
+
5
50
  vals = []
6
51
  30.times { vals << rand(99) }
7
52
  p vals
8
53
 
9
- root_node = Tree::Node.new vals.shift
10
- tree = BinaryTree.new(root_node)
54
+ tree = BinaryTree.new(ChildNode, vals.shift)
11
55
  tree.push vals.shift until vals.empty?
12
56
 
13
- tree.bf_print
57
+ puts tree
14
58
 
15
59
  tree.df_search { |n|
16
60
  puts "visited #{n}"
@@ -19,3 +63,4 @@ tree.df_search { |n|
19
63
  puts
20
64
 
21
65
  p tree
66
+ puts
data/examples/heap.rb CHANGED
@@ -5,34 +5,39 @@ include CompSci
5
5
 
6
6
  puts <<EOF
7
7
  #
8
- # 3 seconds worth of inserts
8
+ # 3 seconds worth of pushes
9
9
  #
10
10
 
11
11
  EOF
12
12
 
13
13
  count = 0
14
14
  start = Timer.now
15
+ start_100k = Timer.now
15
16
  h = Heap.new
16
- elapsed = 0
17
17
 
18
- while elapsed < 3
19
- _answer, push_elapsed = Timer.elapsed { h.push rand 99999 }
18
+ loop {
20
19
  count += 1
21
- puts "%ith push: %0.8f s" % [count, push_elapsed] if count % 10000 == 0
22
20
 
23
- if count % 100000 == 0
24
- start_100k ||= start
25
- push_100k_elapsed = Timer.now - start_100k
26
- puts "-------------"
27
- puts " 100k push: %0.8f s (%ik push / s)" %
28
- [push_100k_elapsed, 100.to_f / push_100k_elapsed]
29
- puts
30
- start_100k = Timer.now
21
+ if count % 10000 == 0
22
+ _answer, push_elapsed = Timer.elapsed { h.push rand 99999 }
23
+ puts "%ith push: %0.8f s" % [count, push_elapsed]
24
+ if count % 100000 == 0
25
+ h.push rand 99999
26
+ push_100k_elapsed = Timer.since start_100k
27
+ puts "-------------"
28
+ puts " 100k push: %0.8f s (%ik push / s)" %
29
+ [push_100k_elapsed, 100.to_f / push_100k_elapsed]
30
+ puts
31
+ start_100k = Timer.now
32
+ end
33
+ else
34
+ h.push rand 99999
31
35
  end
32
- elapsed = Timer.now - start
33
- end
34
36
 
35
- puts "pushed %i items in %0.1f s" % [count, elapsed]
37
+ break if Timer.since(start) > 3
38
+ }
39
+
40
+ puts "pushed %i items in %0.1f s" % [count, Timer.since(start)]
36
41
  puts
37
42
 
38
43
  print "still a heap with #{h.size} items? "
data/examples/timer.rb CHANGED
@@ -18,8 +18,8 @@ puts
18
18
 
19
19
 
20
20
  start = Timer.now
21
- print "running sleep 0.02 (0.5 s): "
22
- _answer, each_et = Timer.loop_average(seconds: 0.5) {
21
+ print "running sleep 0.02 (0.3 s): "
22
+ _answer, each_et = Timer.loop_average(seconds: 0.3) {
23
23
  print '.'
24
24
  sleep 0.02
25
25
  }
@@ -28,15 +28,3 @@ puts "each: %0.3f" % each_et
28
28
  puts "elapsed: %0.3f" % Timer.since(start)
29
29
  puts "cumulative: %0.3f" % Timer.since(overall_start)
30
30
  puts
31
-
32
-
33
- start = Timer.now
34
- print "running sleep 2 (1 s): "
35
- _answer, each_et = Timer.loop_average(seconds: 1) {
36
- print '.'
37
- sleep 2
38
- }
39
- puts
40
- puts "each: %0.3f" % each_et
41
- puts "elapsed: %0.3f" % Timer.since(start)
42
- puts "cumulative: %0.3f" % Timer.since(overall_start)
@@ -1,4 +1,5 @@
1
1
  require 'compsci'
2
+ autoload :Matrix, 'matrix'
2
3
 
3
4
  module CompSci::Fibonacci
4
5
  def self.classic(n)
@@ -16,9 +17,23 @@ module CompSci::Fibonacci
16
17
  cache[n]
17
18
  end
18
19
 
20
+ # traditional
19
21
  def self.dynamic(n)
22
+ a, b = 0, 1
23
+ n.times { a, b = b, a+b }
24
+ a
25
+ end
26
+
27
+ # fails for n == 0
28
+ def self.dynamic_fast(n)
20
29
  a, b = 0, 1
21
30
  (n-1).times { a, b = b, a+b }
22
31
  b
23
32
  end
33
+
34
+ # https://gist.github.com/havenwood/02cf291b809327d96a3f
35
+ # slower than dynamic until around n == 500
36
+ def self.matrix(n)
37
+ (Matrix[[0, 1], [1, 1]] ** n.pred)[1, 1].to_i
38
+ end
24
39
  end
data/lib/compsci/heap.rb CHANGED
@@ -36,13 +36,16 @@ class CompSci::Heap < CompSci::CompleteBinaryTree
36
36
  end
37
37
  end
38
38
 
39
- # append to the array; sift_up
39
+ # append to the array
40
+ # sift_up -- O(log n) on heap size
40
41
  def push(node)
41
42
  @store << node
42
43
  self.sift_up(@store.size - 1)
43
44
  end
44
45
 
45
- # remove from the front of the array; move last node to root; sift_down
46
+ # remove from the front of the array
47
+ # move last node to root
48
+ # sift_down -- O(log n) on heap size
46
49
  def pop
47
50
  node = @store.shift
48
51
  replacement = @store.pop
@@ -56,25 +59,29 @@ class CompSci::Heap < CompSci::CompleteBinaryTree
56
59
  @store.first
57
60
  end
58
61
 
59
- # called recursively; idx represents the node suspected to violate the heap
62
+ # called recursively
63
+ # idx represents the node suspected to violate the heap
64
+ # intended to be O(log n) on heap size
60
65
  def sift_up(idx)
61
66
  return self if idx <= 0
62
67
  pidx = self.class.parent_idx(idx)
63
68
  if !self.heapish?(pidx, idx)
64
- @store[idx], @store[pidx] = @store[pidx], @store[idx] # swap
69
+ @store[idx], @store[pidx] = @store[pidx], @store[idx] # swap
65
70
  self.sift_up(pidx)
66
71
  end
67
72
  self
68
73
  end
69
74
 
70
- # called recursively; idx represents the node suspected to violate the heap
75
+ # called recursively
76
+ # idx represents the node suspected to violate the heap
77
+ # intended to be O(log n) on heap size
71
78
  def sift_down(idx)
72
79
  return self if idx >= @store.size
73
80
  lidx, ridx = self.class.children_idx(idx)
74
- # take the child most likely to be a good parent
81
+ # promote the heapiest child
75
82
  cidx = self.heapish?(lidx, ridx) ? lidx : ridx
76
83
  if !self.heapish?(idx, cidx)
77
- @store[idx], @store[cidx] = @store[cidx], @store[idx] # swap
84
+ @store[idx], @store[cidx] = @store[cidx], @store[idx] # swap
78
85
  self.sift_down(cidx)
79
86
  end
80
87
  self
@@ -85,10 +92,13 @@ class CompSci::Heap < CompSci::CompleteBinaryTree
85
92
  (@store[pidx] <=> @store[cidx]) != (@cmp_val * -1)
86
93
  end
87
94
 
88
- # not used internally; checks that every node satisfies the heap property
95
+ # not used internally
96
+ # checks that every node satisfies the heap property
97
+ # calls heapish? on idx's children and then heap? on them recursively
89
98
  def heap?(idx: 0)
90
99
  check_children = []
91
100
  self.class.children_idx(idx).each { |cidx|
101
+ # cidx is arithmetically produced; the corresponding child may not exist
92
102
  if cidx < @store.size
93
103
  return false unless self.heapish?(idx, cidx)
94
104
  check_children << cidx
data/lib/compsci/tree.rb CHANGED
@@ -1,26 +1,67 @@
1
1
  require 'compsci'
2
2
 
3
3
  module CompSci
4
- class Tree
5
- attr_reader :root, :child_slots
4
+ class Node
5
+ attr_accessor :value
6
+ attr_reader :children
7
+
8
+ def initialize(value)
9
+ @value = value
10
+ @children = []
11
+ # @metadata = {}
12
+ end
6
13
 
7
- def initialize(root_node, child_slots: 2)
8
- @root = root_node
9
- @child_slots = child_slots
10
- @open_parent = @root
14
+ def add_child(node)
15
+ @children << node
11
16
  end
12
17
 
13
- def push(value)
14
- self.open_parent.new_child value
18
+ def new_child(value)
19
+ self.add_child self.class.new(value)
15
20
  end
16
21
 
17
- def open_parent?(node)
18
- node.children.size < @child_slots
22
+ def add_parent(node)
23
+ node.add_child(self)
19
24
  end
20
25
 
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) }
26
+ def to_s
27
+ @value.to_s
28
+ end
29
+
30
+ def inspect
31
+ "#<%s:0x%0xi @value=%s @children=[%s]>" %
32
+ [self.class,
33
+ self.object_id,
34
+ self.to_s,
35
+ @children.map(&:to_s).join(', ')]
36
+ end
37
+ end
38
+
39
+ # like Node but with a reference to its parent
40
+ class ChildNode < Node
41
+ attr_accessor :parent
42
+
43
+ def initialize(value)
44
+ @parent = nil
45
+ super(value)
46
+ end
47
+
48
+ def add_child(node)
49
+ node.parent ||= self
50
+ raise "node has a parent: #{node.parent}" if node.parent != self
51
+ super(node)
52
+ end
53
+
54
+ def add_parent(node)
55
+ @parent = node
56
+ super(node)
57
+ end
58
+ end
59
+
60
+ class Tree
61
+ attr_reader :root
62
+
63
+ def initialize(klass, val)
64
+ @root = klass.new val
24
65
  end
25
66
 
26
67
  def df_search(node: nil, &blk)
@@ -33,13 +74,6 @@ module CompSci
33
74
  nil
34
75
  end
35
76
 
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
77
  def bf_search(node: nil, &blk)
44
78
  node ||= @root
45
79
  destinations = [node]
@@ -51,61 +85,64 @@ module CompSci
51
85
  nil
52
86
  end
53
87
 
54
- class Node
55
- attr_accessor :value, :parent
56
- attr_reader :children
88
+ def df_search_generic(node: nil, &blk)
89
+ # Perform pre-order operation
90
+ # children.each { Perform in-order operation }
91
+ # Perform post-order operation
92
+ puts "not defined yet"
93
+ end
94
+ end
57
95
 
58
- def initialize(value)
59
- @value = value
60
- @parent = nil
61
- @children = []
62
- # @metadata = {}
63
- end
96
+ class NaryTree < Tree
97
+ attr_reader :child_slots
64
98
 
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
99
+ def initialize(klass, val, child_slots:)
100
+ super(klass, val)
101
+ raise "#{klass}#parent required" unless @root.respond_to? :parent
102
+ @child_slots = child_slots
103
+ end
70
104
 
71
- def new_child(value)
72
- self.add_child self.class.new(value)
73
- end
105
+ def open_parent?(node)
106
+ node.children.size < @child_slots
107
+ end
74
108
 
75
- def add_parent(node)
76
- @parent = node
77
- node.add_child(self)
78
- end
109
+ def open_parent
110
+ @open_parent ||= @root
111
+ return @open_parent if self.open_parent?(@open_parent)
79
112
 
80
- def to_s
81
- @value.to_s
82
- end
113
+ # TODO: ugh, there must be a better way, this is O(n)
83
114
 
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(', ')]
115
+ # try siblings first
116
+ if @open_parent.parent
117
+ @open_parent.parent.children.each { |c|
118
+ return @open_parent = c if self.open_parent?(c)
119
+ }
90
120
  end
121
+ @open_parent = self.bf_search { |n| self.open_parent?(n) }
122
+ end
123
+
124
+ def push(value)
125
+ self.open_parent.new_child value
91
126
  end
92
127
  end
93
128
 
94
- class BinaryTree < Tree
95
- def initialize(root_node)
96
- super(root_node, child_slots: 2)
129
+ class BinaryTree < NaryTree
130
+ def initialize(klass, val)
131
+ super(klass, val, child_slots: 2)
97
132
  end
98
133
 
99
- def bf_print(node: nil, width: 80)
134
+ def to_s(node: nil, width: 80)
100
135
  count = 0
136
+ str = ''
101
137
  self.bf_search(node: node) { |n|
102
138
  count += 1
103
139
  level = Math.log(count, 2).floor
104
140
  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, ' ')
141
+ str += "\n" if 2**level == count and count > 1
142
+ str += n.to_s.ljust(block_width / 2, ' ').rjust(block_width, ' ')
143
+ false # keep searching to visit every node
107
144
  }
108
- puts
145
+ str
109
146
  end
110
147
  end
111
148
 
@@ -1,4 +1,4 @@
1
- require 'compsci/fib'
1
+ require 'compsci/fibonacci'
2
2
  require 'minitest/autorun'
3
3
  require 'minitest/benchmark'
4
4
 
@@ -14,13 +14,14 @@ CACHE_RANGE = [100, 1000, 10000, 100000]
14
14
  # this causes churn at the process level and impacts other benchmarks
15
15
  # DYNAMIC_RANGE = [100, 1000, 10000, 100000, 200000, 500000]
16
16
  DYNAMIC_RANGE = [100, 1000, 10000, 100000]
17
+ MATRIX_RANGE = [100, 1000, 10000, 100000]
17
18
 
18
19
  #SPEC_BENCHMARK = true
19
- #CLASS_BENCHMARK = false
20
- #BENCHMARK_IPS = true
20
+ #CLASS_BENCHMARK = true
21
+ BENCHMARK_IPS = true
21
22
  SPEC_BENCHMARK = false
22
- CLASS_BENCHMARK = true
23
- BENCHMARK_IPS = false
23
+ CLASS_BENCHMARK = false
24
+ #BENCHMARK_IPS = false
24
25
 
25
26
 
26
27
  if SPEC_BENCHMARK
@@ -67,6 +68,17 @@ if SPEC_BENCHMARK
67
68
  Fibonacci.dynamic(n)
68
69
  end
69
70
  end
71
+
72
+ describe "Fibonacci.matrix Benchmark" do
73
+ bench_range do
74
+ MATRIX_RANGE
75
+ end
76
+
77
+ fd = ["Fibonacci.matrix (linear, 0.99)", 0.99]
78
+ bench_performance_linear(*fd) do |n|
79
+ Fibonacci.matrix(n)
80
+ end
81
+ end
70
82
  end
71
83
 
72
84
  if CLASS_BENCHMARK
@@ -89,6 +101,7 @@ end
89
101
  if BENCHMARK_IPS
90
102
  require 'benchmark/ips'
91
103
 
104
+ # recursive benchmarks with low N; iterative for comparison
92
105
  Benchmark.ips do |b|
93
106
  b.config time: 3, warmup: 0.5
94
107
  num = 25
@@ -105,10 +118,30 @@ if BENCHMARK_IPS
105
118
  Fibonacci.cache_iterative(num)
106
119
  }
107
120
 
121
+ b.compare!
122
+ end
123
+
124
+ # nonrecursive benchmarks with high N
125
+ Benchmark.ips do |b|
126
+ b.config time: 3, warmup: 0.5
127
+ num = 500
128
+
129
+ b.report("Fibonacci.cache_iterative(#{num})") {
130
+ Fibonacci.cache_iterative(num)
131
+ }
132
+
108
133
  b.report("Fibonacci.dynamic(#{num})") {
109
134
  Fibonacci.dynamic(num)
110
135
  }
111
136
 
137
+ b.report("Fibonacci.dynamic_fast(#{num})") {
138
+ Fibonacci.dynamic_fast(num)
139
+ }
140
+
141
+ b.report("Fibonacci.matrix(#{num})") {
142
+ Fibonacci.matrix(num)
143
+ }
144
+
112
145
  b.compare!
113
146
  end
114
147
  end
data/test/bench/tree.rb CHANGED
@@ -4,7 +4,7 @@ require 'minitest/benchmark'
4
4
 
5
5
  include CompSci
6
6
 
7
- describe "Tree#push Benchmark" do
7
+ describe "BinaryTree#push Benchmark" do
8
8
  bench_range do
9
9
  # note, 5000 takes way too long and is definitely not constant time
10
10
  # TODO: BUG?
@@ -12,8 +12,15 @@ describe "Tree#push Benchmark" do
12
12
  [10, 100, 1000, 2000]
13
13
  end
14
14
 
15
- bench_performance_constant "Tree#push (constant)" do |n|
16
- tree = Tree.new Tree::Node.new 42
15
+ bench_performance_constant "BinaryTree#push (constant)" do |n|
16
+ tree = BinaryTree.new(ChildNode, 42)
17
+ n.times { tree.push rand 99 }
18
+ end
19
+
20
+ bench_performance_linear "BinaryTree#push (linear)" do |n|
21
+ skip "this fails with r^2 around 0.91"
22
+
23
+ tree = BinaryTree.new ChildNode.new 42
17
24
  n.times { tree.push rand 99 }
18
25
  end
19
26
  end
data/test/fibonacci.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'compsci/fibonacci'
2
+ require 'minitest/autorun'
3
+
4
+ include CompSci
5
+
6
+ describe Fibonacci do
7
+ before do
8
+ @answers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
9
+ end
10
+
11
+ it "must calculate fib(0..10)" do
12
+ @answers.each_with_index { |ans, i|
13
+ Fibonacci.classic(i).must_equal ans
14
+ Fibonacci.cache_recursive(i).must_equal ans
15
+ Fibonacci.cache_iterative(i).must_equal ans
16
+ Fibonacci.dynamic(i).must_equal ans
17
+ Fibonacci.matrix(i).must_equal ans
18
+ }
19
+ end
20
+ end
data/test/fit.rb CHANGED
@@ -3,19 +3,23 @@ require 'minitest/autorun'
3
3
 
4
4
  include CompSci
5
5
 
6
+ def noise # range: -0.5 to 0.5
7
+ rand - 0.5
8
+ end
9
+
6
10
  describe Fit do
7
11
  before do
8
12
  @xs = [1, 2, 5, 10, 20, 50, 100, 200, 500]
9
13
  end
10
14
 
11
- describe "sigma" do
15
+ describe "Fit.sigma" do
12
16
  it "must answer correctly" do
13
17
  Fit.sigma([1, 2, 3]).must_equal 6
14
18
  Fit.sigma([1, 2, 3]) { |n| n ** 2 }.must_equal 14
15
19
  end
16
20
  end
17
21
 
18
- describe "error" do
22
+ describe "Fit.error" do
19
23
  it "must calculate r^2" do
20
24
  Fit.error([[1, 1], [2, 2], [3, 3]]) { |x| x }.must_equal 1.0
21
25
  Fit.error([[1, 1], [2, 2], [3, 4]]) { |x| x }.must_be_close_to 0.785
@@ -23,22 +27,28 @@ describe Fit do
23
27
  end
24
28
 
25
29
  # y = a
26
- describe "constant" do
30
+ describe "Fit.constant" do
31
+ it "must accept constant data" do
32
+ [0, 1, 10, 100, 1000, 9999].each { |a|
33
+ y_bar, variance = Fit.constant(@xs, @xs.map { |x| a })
34
+ y_bar.must_equal a
35
+ variance.must_equal 0
36
+ }
37
+ end
38
+
27
39
  # note, this test can possibly fail depending on the uniformity of
28
40
  # rand's output for our sample
29
- it "must accept constant data" do
41
+ it "must accept noisy constant data" do
30
42
  [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
43
+ y_bar, variance = Fit.constant(@xs, @xs.map { |x| a + noise() })
34
44
  y_bar.must_be_close_to a, 0.3
35
- var_val.must_be_close_to 0.1, 0.09
45
+ (variance / @xs.size).must_be_close_to 0.1, 0.09
36
46
  }
37
47
  end
38
48
  end
39
49
 
40
50
  # y = a + b*ln(x)
41
- describe "logarithmic" do
51
+ describe "Fit.logarithmic" do
42
52
  it "must accept logarithmic data" do
43
53
  [-9999, -2000, -500, -0.01, 0.01, 500, 2000, 9999].each { |a|
44
54
  [-9999, -2000, -500, -0.01, 0.01, 500, 2000, 9999].each { |b|
@@ -52,7 +62,7 @@ describe Fit do
52
62
  end
53
63
 
54
64
  # y = a + bx
55
- describe "linear" do
65
+ describe "Fit.linear" do
56
66
  it "must accept linear data" do
57
67
  [-9999, -2000, -500, -0.01, 0.01, 500, 2000, 9999].each { |a|
58
68
  [-9999, -2000, -500, -0.01, 0.01, 500, 2000, 9999].each { |b|
@@ -64,21 +74,22 @@ describe Fit do
64
74
  }
65
75
  end
66
76
 
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
- #
77
+ it "must accept constant data" do
78
+ [0, 1, 10, 100, 1000, 9999].each { |a|
79
+ ary = Fit.linear(@xs, @xs.map { |x| a })
80
+ ary[0].must_equal a
81
+ ary[1].must_equal 0
82
+ ary[2].nan?.must_equal true
83
+ }
84
+ end
85
+
74
86
  # note, this test can possibly fail depending on the uniformity of
75
87
  # rand's output for our sample
76
88
  #
77
- it "must accept constant data" do
89
+ it "must accept noisy constant data" do
78
90
  r2s = []
79
91
  [0, 1, 10, 100, 1000, 9999].each { |a|
80
- ys = @xs.map { |x| a + (rand - 0.5) }
81
- ary = Fit.linear(@xs, ys)
92
+ ary = Fit.linear(@xs, @xs.map { |x| a + noise() })
82
93
  ary[0].must_be_close_to a, 0.4
83
94
  ary[1].must_be_close_to 0, 0.05
84
95
  r2s << ary[2]
@@ -87,52 +98,23 @@ describe Fit do
87
98
  mean_r2.must_be_close_to 0.15, 0.15
88
99
  end
89
100
 
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
- #
101
+ it "must reject x^2" do
102
+ skip "it does not reject x^2 at r^2 < 0.99"
103
+ xs = [1, 10, 100, 1000]
104
+ _a, _b, r2 = Fit.linear(xs, xs.map { |x| x**2 })
105
+ r2.must_be :<, 0.99
106
+ end
111
107
 
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
108
+ it "must reject x^3" do
109
+ skip "it does not reject x^3 at r^2 < 0.99"
110
+ xs = [1, 10, 100, 1000]
111
+ _a, _b, r2 = Fit.linear(xs, xs.map { |x| x**3 })
112
+ r2.must_be :<, 0.99
131
113
  end
132
114
  end
133
115
 
134
116
  # y = ae^(bx)
135
- describe "exponential" do
117
+ describe "Fit.exponential" do
136
118
  it "must accept exponential data" do
137
119
  [0.001, 7.5, 500, 1000, 5000, 9999].each { |a|
138
120
  [-1.4, -1.1, -0.1, 0.01, 0.5, 0.75].each { |b|
@@ -146,7 +128,7 @@ describe Fit do
146
128
  end
147
129
 
148
130
  # y = ax^b
149
- describe "power" do
131
+ describe "Fit.power" do
150
132
  it "must accept power data" do
151
133
  [0.01, 7.5, 500, 1000, 5000, 9999].each { |a|
152
134
  [-114, -100, -10, -0.5, -0.1, 0.1, 0.75, 10, 50, 60].each { |b|
data/test/timer.rb CHANGED
@@ -23,26 +23,27 @@ describe Timer do
23
23
  describe "loop_average" do
24
24
  it "return the block value and a positive number" do
25
25
  start = Timer.now
26
- answer, avg_et = Timer.loop_average(seconds: 0.25) {
26
+ answer, avg_et = Timer.loop_average(seconds: 0.1) {
27
27
  sleep 0.01
28
28
  :foo
29
29
  }
30
30
  answer.must_equal :foo
31
31
  avg_et.must_be_close_to 0.01, 0.005
32
- Timer.since(start).must_be_close_to 0.3, 0.05
32
+ Timer.since(start).must_be_close_to 0.15, 0.05
33
33
  end
34
34
 
35
35
  it "must repeat short loops and stop on time" do
36
+ # see above, Timer.since(start)
36
37
  true.must_equal true
37
38
  end
38
39
 
39
40
  it "must not interrupt long loops" do
40
41
  start = Timer.now
41
- _answer, avg_et = Timer.loop_average(seconds: 0.1) {
42
- sleep 0.25
42
+ _answer, avg_et = Timer.loop_average(seconds: 0.01) {
43
+ sleep 0.1
43
44
  }
44
45
  Timer.since(start).must_be_close_to avg_et, 0.05
45
- avg_et.must_be_close_to 0.3, 0.05
46
+ avg_et.must_be_close_to 0.15, 0.05
46
47
  end
47
48
  end
48
49
  end
data/test/tree.rb CHANGED
@@ -3,14 +3,135 @@ require 'minitest/autorun'
3
3
 
4
4
  include CompSci
5
5
 
6
+ describe Node do
7
+ before do
8
+ @martin_sheen = Node.new 'martin'
9
+ @charlie_sheen = Node.new 'charlie'
10
+ @emilio_estevez = Node.new 'emilio'
11
+ end
12
+
13
+ it "must start with no children" do
14
+ [@martin_sheen, @charlie_sheen, @emilio_estevez].each { |n|
15
+ n.children.must_be_empty
16
+ }
17
+ end
18
+
19
+ it "must track children" do
20
+ @charlie_sheen.add_parent(@martin_sheen)
21
+ @martin_sheen.children.must_include @charlie_sheen
22
+
23
+ @martin_sheen.children.wont_include @emilio_estevez
24
+ @martin_sheen.add_child @emilio_estevez
25
+ @martin_sheen.children.must_include @emilio_estevez
26
+ end
27
+
28
+ it "must create children from scalars" do
29
+ @martin_sheen.new_child 'fake_emilio'
30
+ @martin_sheen.children.size.must_equal 1
31
+ @martin_sheen.children.first.value.must_equal 'fake_emilio'
32
+ @martin_sheen.children.wont_include @emilio_estevez
33
+ end
34
+ end
35
+
36
+ describe ChildNode do
37
+ before do
38
+ @martin_sheen = ChildNode.new 'martin'
39
+ @charlie_sheen = ChildNode.new 'charlie'
40
+ @emilio_estevez = ChildNode.new 'emilio'
41
+ end
42
+
43
+ it "must start with neither parent nor children" do
44
+ [@martin_sheen, @charlie_sheen, @emilio_estevez].each { |n|
45
+ n.parent.nil?.must_equal true
46
+ n.children.must_be_empty
47
+ }
48
+ end
49
+
50
+ it "must track parent and children" do
51
+ @charlie_sheen.add_parent(@martin_sheen)
52
+ @charlie_sheen.parent.must_equal @martin_sheen
53
+ @martin_sheen.children.must_include @charlie_sheen
54
+
55
+ @martin_sheen.children.wont_include @emilio_estevez
56
+ @martin_sheen.add_child @emilio_estevez
57
+ @martin_sheen.children.must_include @emilio_estevez
58
+ @emilio_estevez.parent.must_equal @martin_sheen
59
+ end
60
+
61
+ it "must create children from scalars" do
62
+ @martin_sheen.new_child 'fake_emilio'
63
+ @martin_sheen.children.size.must_equal 1
64
+ @martin_sheen.children.first.value.must_equal 'fake_emilio'
65
+ @martin_sheen.children.wont_include @emilio_estevez
66
+ end
67
+ end
68
+
6
69
  describe Tree do
7
70
  before do
8
- @node = Tree::Node.new 42
9
- @tree = Tree.new(@node, child_slots: 3)
71
+ @tree = Tree.new(Node, 42)
72
+ @vals = Array.new(99) { rand 99 }
73
+ end
74
+
75
+ it "is populated via the root node" do
76
+ @vals.each { |v| @tree.root.new_child v }
77
+ @tree.root.children.size.must_equal @vals.size
78
+ end
79
+
80
+ it "does depth_first search" do
81
+ vals = (0..30).to_a
82
+ tree = Tree.new(Node, vals.shift)
83
+ tree.root.new_child vals.shift
84
+ tree.root.new_child vals.shift
85
+ tree.root.children.each { |c|
86
+ c.new_child vals.shift
87
+ c.new_child vals.shift
88
+
89
+ c.children.each { |cc|
90
+ cc.new_child vals.shift
91
+ cc.new_child vals.shift
92
+ }
93
+ }
94
+
95
+ visited = []
96
+ tree.df_search { |n|
97
+ visited << n.value
98
+ false
99
+ }
100
+ visited.wont_be_empty
101
+ visited.must_equal [0, 1, 3, 5, 6, 4, 7, 8, 2, 9, 11, 12, 10, 13, 14]
102
+ end
103
+
104
+ it "does breadth_first search" do
105
+ vals = (0..30).to_a
106
+ tree = Tree.new(Node, vals.shift)
107
+ tree.root.new_child vals.shift
108
+ tree.root.new_child vals.shift
109
+ tree.root.children.each { |c|
110
+ c.new_child vals.shift
111
+ c.new_child vals.shift
112
+
113
+ c.children.each { |cc|
114
+ cc.new_child vals.shift
115
+ cc.new_child vals.shift
116
+ }
117
+ }
118
+
119
+ visited = []
120
+ tree.bf_search { |n|
121
+ visited << n.value
122
+ false
123
+ }
124
+ visited.wont_be_empty
125
+ visited.must_equal [0, 1, 2, 3, 4, 9, 10, 5, 6, 7, 8, 11, 12, 13, 14]
126
+ end
127
+ end
128
+
129
+ describe NaryTree do
130
+ before do
131
+ @tree = NaryTree.new(ChildNode, 42, child_slots: 3)
10
132
  end
11
133
 
12
134
  it "must have an open parent" do
13
- @tree.open_parent?(@node).must_equal true
14
135
  @tree.open_parent?(@tree.root).must_equal true
15
136
  @tree.open_parent?(@tree.open_parent).must_equal true
16
137
  end
@@ -24,7 +145,7 @@ describe Tree do
24
145
 
25
146
  describe "searching" do
26
147
  before do
27
- @tree = Tree.new(Tree::Node.new 42)
148
+ @tree = NaryTree.new(ChildNode, 42, child_slots: 2)
28
149
  99.times { |i| @tree.push i }
29
150
  end
30
151
 
@@ -76,59 +197,24 @@ describe Tree do
76
197
  count.must_equal 83
77
198
  end
78
199
  end
79
-
80
- describe Tree::Node do
81
- before do
82
- @martin_sheen = Tree::Node.new 'martin'
83
- @charlie_sheen = Tree::Node.new 'charlie'
84
- @emilio_estevez = Tree::Node.new 'emilio'
85
- end
86
-
87
- it "must start with no relations" do
88
- [@martin_sheen, @charlie_sheen, @emilio_estevez].each { |n|
89
- n.parent.nil?.must_equal true
90
- n.children.must_be_empty
91
- }
92
- end
93
-
94
- it "must allow relations" do
95
- @charlie_sheen.add_parent(@martin_sheen)
96
- @charlie_sheen.parent.must_equal @martin_sheen
97
- @martin_sheen.children.must_include @charlie_sheen
98
-
99
- @martin_sheen.children.wont_include @emilio_estevez
100
- @martin_sheen.add_child @emilio_estevez
101
- @martin_sheen.children.must_include @emilio_estevez
102
- @emilio_estevez.parent.must_equal @martin_sheen
103
- end
104
-
105
- it "must create children from scalars" do
106
- @martin_sheen.new_child 'fake_emilio'
107
- @martin_sheen.children.size.must_equal 1
108
- @martin_sheen.children.first.value.must_equal 'fake_emilio'
109
- @martin_sheen.children.wont_include @emilio_estevez
110
- end
111
- end
112
200
  end
113
201
 
114
202
  describe BinaryTree do
115
203
  before do
116
- @tree = BinaryTree.new(Tree::Node.new 42)
204
+ @tree = BinaryTree.new(ChildNode, 42)
117
205
  end
118
206
 
119
207
  it "must have 2 child_slots" do
120
208
  @tree.child_slots.must_equal 2
121
209
  end
122
210
 
123
- it "must bf_print" do
124
- 31.times { @tree.push rand 99 }
125
- out, err = capture_io do
126
- @tree.bf_print
127
- end
128
- line_count = out.split("\n").size
129
- line_count.must_be :>, 4
130
- line_count.must_be :<, 7
131
- err.must_be_empty
211
+ it "must to_s" do
212
+ item_count = 31
213
+ # tree already has a root node
214
+ (item_count - 1).times { @tree.push rand 99 }
215
+ str = @tree.to_s
216
+ line_count = str.split("\n").size
217
+ line_count.must_equal Math.log(item_count + 1, 2).ceil
132
218
  end
133
219
  end
134
220
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: compsci
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.1
4
+ version: 0.0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rick Hull
@@ -38,15 +38,15 @@ files:
38
38
  - examples/heap.rb
39
39
  - examples/timer.rb
40
40
  - lib/compsci.rb
41
- - lib/compsci/fib.rb
41
+ - lib/compsci/fibonacci.rb
42
42
  - lib/compsci/fit.rb
43
43
  - lib/compsci/heap.rb
44
44
  - lib/compsci/timer.rb
45
45
  - lib/compsci/tree.rb
46
- - test/bench/fib.rb
46
+ - test/bench/fibonacci.rb
47
47
  - test/bench/heap.rb
48
48
  - test/bench/tree.rb
49
- - test/fib.rb
49
+ - test/fibonacci.rb
50
50
  - test/fit.rb
51
51
  - test/heap.rb
52
52
  - test/timer.rb
data/test/fib.rb DELETED
@@ -1,13 +0,0 @@
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