compsci 0.0.1.1 → 0.0.2.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 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