compsci 0.0.3.1 → 0.1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,137 +1,137 @@
1
- require 'compsci'
2
-
3
- module CompSci::Fit
4
- #
5
- # functions below originally copied from https://github.com/seattlrb/minitest
6
- #
7
-
8
- ##
9
- # Enumerates over +enum+ mapping +block+ if given, returning the
10
- # sum of the result. Eg:
11
- #
12
- # sigma([1, 2, 3]) # => 1 + 2 + 3 => 7
13
- # sigma([1, 2, 3]) { |n| n ** 2 } # => 1 + 4 + 9 => 14
14
-
15
- def self.sigma enum, &block
16
- enum = enum.map(&block) if block
17
- enum.inject { |sum, n| sum + n }
18
- end
19
-
20
- ##
21
- # Takes an array of x/y pairs and calculates the general R^2 value to
22
- # measure fit against a predictive function, which is the block supplied
23
- # to error:
24
- #
25
- # e.g. error(xys) { |x| 5 + 2 * x }
26
- #
27
- # See: http://en.wikipedia.org/wiki/Coefficient_of_determination
28
- #
29
-
30
- def self.error xys, &blk
31
- y_bar = sigma(xys) { |_, y| y } / xys.size.to_f
32
- ss_tot = sigma(xys) { |_, y| (y - y_bar) ** 2 }
33
- ss_res = sigma(xys) { |x, y| (yield(x) - y) ** 2 }
34
-
35
- 1 - (ss_res / ss_tot)
36
- end
37
-
38
- ##
39
- # Fits the functional form: a (+ 0x)
40
- #
41
- # Takes x and y values and returns [a, variance]
42
- #
43
-
44
- def self.constant xs, ys
45
- # written by Rick
46
- y_bar = sigma(ys) / ys.size.to_f
47
- variance = sigma(ys) { |y| (y - y_bar) ** 2 }
48
- [y_bar, variance]
49
- end
50
-
51
- ##
52
- # To fit a functional form: y = a + b*ln(x).
53
- #
54
- # Takes x and y values and returns [a, b, r^2].
55
- #
56
- # See: http://mathworld.wolfram.com/LeastSquaresFittingLogarithmic.html
57
-
58
- def self.logarithmic xs, ys
59
- n = xs.size
60
- xys = xs.zip(ys)
61
- slnx2 = sigma(xys) { |x, _| Math.log(x) ** 2 }
62
- slnx = sigma(xys) { |x, _| Math.log(x) }
63
- sylnx = sigma(xys) { |x, y| y * Math.log(x) }
64
- sy = sigma(xys) { |_, y| y }
65
-
66
- c = n * slnx2 - slnx ** 2
67
- b = ( n * sylnx - sy * slnx ) / c
68
- a = (sy - b * slnx) / n
69
-
70
- return a, b, self.error(xys) { |x| a + b * Math.log(x) }
71
- end
72
-
73
- ##
74
- # Fits the functional form: a + bx.
75
- #
76
- # Takes x and y values and returns [a, b, r^2].
77
- #
78
- # See: http://mathworld.wolfram.com/LeastSquaresFitting.html
79
-
80
- def self.linear xs, ys
81
- n = xs.size
82
- xys = xs.zip(ys)
83
- sx = sigma xs
84
- sy = sigma ys
85
- sx2 = sigma(xs) { |x| x ** 2 }
86
- sxy = sigma(xys) { |x, y| x * y }
87
-
88
- c = n * sx2 - sx**2
89
- a = (sy * sx2 - sx * sxy) / c
90
- b = ( n * sxy - sx * sy ) / c
91
-
92
- return a, b, self.error(xys) { |x| a + b * x }
93
- end
94
-
95
- ##
96
- # To fit a functional form: y = ae^(bx).
97
- #
98
- # Takes x and y values and returns [a, b, r^2].
99
- #
100
- # See: http://mathworld.wolfram.com/LeastSquaresFittingExponential.html
101
-
102
- def self.exponential xs, ys
103
- n = xs.size
104
- xys = xs.zip(ys)
105
- sxlny = sigma(xys) { |x, y| x * Math.log(y) }
106
- slny = sigma(xys) { |_, y| Math.log(y) }
107
- sx2 = sigma(xys) { |x, _| x * x }
108
- sx = sigma xs
109
-
110
- c = n * sx2 - sx ** 2
111
- a = (slny * sx2 - sx * sxlny) / c
112
- b = ( n * sxlny - sx * slny ) / c
113
-
114
- return Math.exp(a), b, self.error(xys) { |x| Math.exp(a + b * x) }
115
- end
116
-
117
- ##
118
- # To fit a functional form: y = ax^b.
119
- #
120
- # Takes x and y values and returns [a, b, r^2].
121
- #
122
- # See: http://mathworld.wolfram.com/LeastSquaresFittingPowerLaw.html
123
-
124
- def self.power xs, ys
125
- n = xs.size
126
- xys = xs.zip(ys)
127
- slnxlny = sigma(xys) { |x, y| Math.log(x) * Math.log(y) }
128
- slnx = sigma(xs) { |x | Math.log(x) }
129
- slny = sigma(ys) { | y| Math.log(y) }
130
- slnx2 = sigma(xs) { |x | Math.log(x) ** 2 }
131
-
132
- b = (n * slnxlny - slnx * slny) / (n * slnx2 - slnx ** 2)
133
- a = (slny - b * slnx) / n
134
-
135
- return Math.exp(a), b, self.error(xys) { |x| (Math.exp(a) * (x ** b)) }
1
+ module CompSci
2
+ module Fit
3
+ #
4
+ # functions below originally from https://github.com/seattlrb/minitest
5
+ #
6
+
7
+ ##
8
+ # Enumerates over +enum+ mapping +block+ if given, returning the
9
+ # sum of the result. Eg:
10
+ #
11
+ # sigma([1, 2, 3]) # => 1 + 2 + 3 => 7
12
+ # sigma([1, 2, 3]) { |n| n ** 2 } # => 1 + 4 + 9 => 14
13
+
14
+ def self.sigma enum, &block
15
+ enum = enum.map(&block) if block
16
+ enum.inject { |sum, n| sum + n }
17
+ end
18
+
19
+ ##
20
+ # Takes an array of x/y pairs and calculates the general R^2 value to
21
+ # measure fit against a predictive function, which is the block supplied
22
+ # to error:
23
+ #
24
+ # e.g. error(xys) { |x| 5 + 2 * x }
25
+ #
26
+ # See: http://en.wikipedia.org/wiki/Coefficient_of_determination
27
+ #
28
+
29
+ def self.error xys, &blk
30
+ y_bar = sigma(xys) { |_, y| y } / xys.size.to_f
31
+ ss_tot = sigma(xys) { |_, y| (y - y_bar) ** 2 }
32
+ ss_res = sigma(xys) { |x, y| (yield(x) - y) ** 2 }
33
+
34
+ 1 - (ss_res / ss_tot)
35
+ end
36
+
37
+ ##
38
+ # Fits the functional form: a (+ 0x)
39
+ #
40
+ # Takes x and y values and returns [a, variance]
41
+ #
42
+
43
+ def self.constant xs, ys
44
+ # written by Rick
45
+ y_bar = sigma(ys) / ys.size.to_f
46
+ variance = sigma(ys) { |y| (y - y_bar) ** 2 }
47
+ [y_bar, variance]
48
+ end
49
+
50
+ ##
51
+ # To fit a functional form: y = a + b*ln(x).
52
+ #
53
+ # Takes x and y values and returns [a, b, r^2].
54
+ #
55
+ # See: http://mathworld.wolfram.com/LeastSquaresFittingLogarithmic.html
56
+
57
+ def self.logarithmic xs, ys
58
+ n = xs.size
59
+ xys = xs.zip(ys)
60
+ slnx2 = sigma(xys) { |x, _| Math.log(x) ** 2 }
61
+ slnx = sigma(xys) { |x, _| Math.log(x) }
62
+ sylnx = sigma(xys) { |x, y| y * Math.log(x) }
63
+ sy = sigma(xys) { |_, y| y }
64
+
65
+ c = n * slnx2 - slnx ** 2
66
+ b = ( n * sylnx - sy * slnx ) / c
67
+ a = (sy - b * slnx) / n
68
+
69
+ return a, b, self.error(xys) { |x| a + b * Math.log(x) }
70
+ end
71
+
72
+ ##
73
+ # Fits the functional form: a + bx.
74
+ #
75
+ # Takes x and y values and returns [a, b, r^2].
76
+ #
77
+ # See: http://mathworld.wolfram.com/LeastSquaresFitting.html
78
+
79
+ def self.linear xs, ys
80
+ n = xs.size
81
+ xys = xs.zip(ys)
82
+ sx = sigma xs
83
+ sy = sigma ys
84
+ sx2 = sigma(xs) { |x| x ** 2 }
85
+ sxy = sigma(xys) { |x, y| x * y }
86
+
87
+ c = n * sx2 - sx**2
88
+ a = (sy * sx2 - sx * sxy) / c
89
+ b = ( n * sxy - sx * sy ) / c
90
+
91
+ return a, b, self.error(xys) { |x| a + b * x }
92
+ end
93
+
94
+ ##
95
+ # To fit a functional form: y = ae^(bx).
96
+ #
97
+ # Takes x and y values and returns [a, b, r^2].
98
+ #
99
+ # See: http://mathworld.wolfram.com/LeastSquaresFittingExponential.html
100
+
101
+ def self.exponential xs, ys
102
+ n = xs.size
103
+ xys = xs.zip(ys)
104
+ sxlny = sigma(xys) { |x, y| x * Math.log(y) }
105
+ slny = sigma(xys) { |_, y| Math.log(y) }
106
+ sx2 = sigma(xys) { |x, _| x * x }
107
+ sx = sigma xs
108
+
109
+ c = n * sx2 - sx ** 2
110
+ a = (slny * sx2 - sx * sxlny) / c
111
+ b = ( n * sxlny - sx * slny ) / c
112
+
113
+ return Math.exp(a), b, self.error(xys) { |x| Math.exp(a + b * x) }
114
+ end
115
+
116
+ ##
117
+ # To fit a functional form: y = ax^b.
118
+ #
119
+ # Takes x and y values and returns [a, b, r^2].
120
+ #
121
+ # See: http://mathworld.wolfram.com/LeastSquaresFittingPowerLaw.html
122
+
123
+ def self.power xs, ys
124
+ n = xs.size
125
+ xys = xs.zip(ys)
126
+ slnxlny = sigma(xys) { |x, y| Math.log(x) * Math.log(y) }
127
+ slnx = sigma(xs) { |x | Math.log(x) }
128
+ slny = sigma(ys) { | y| Math.log(y) }
129
+ slnx2 = sigma(xs) { |x | Math.log(x) ** 2 }
130
+
131
+ b = (n * slnxlny - slnx * slny) / (n * slnx2 - slnx ** 2)
132
+ a = (slny - b * slnx) / n
133
+
134
+ return Math.exp(a), b, self.error(xys) { |x| (Math.exp(a) * (x ** b)) }
135
+ end
136
136
  end
137
137
  end
@@ -1,6 +1,4 @@
1
- require 'compsci/tree'
2
-
3
- include CompSci
1
+ require 'compsci/complete_tree'
4
2
 
5
3
  # A Heap is a partially sorted, complete N-ary tree with the property:
6
4
  # * Every node has a value larger (or smaller) than that of its children
@@ -20,7 +18,7 @@ include CompSci
20
18
  # swap nodes at each layer of the tree, and there are log(n, base b) layers
21
19
  # to the tree.
22
20
  #
23
- class Heap < CompleteNaryTree
21
+ class CompSci::Heap < CompSci::CompleteNaryTree
24
22
  # * defaults to a MaxHeap, with the largest node at the root
25
23
  # * specify a minheap with minheap: true or cmp_val: -1
26
24
  #
@@ -39,8 +37,8 @@ class Heap < CompleteNaryTree
39
37
  # * sift_up -- O(log n) on heap size
40
38
  #
41
39
  def push(node)
42
- @store << node
43
- self.sift_up(@store.size - 1)
40
+ @array.push(node)
41
+ self.sift_up(@array.size - 1)
44
42
  end
45
43
 
46
44
  # * remove from the front of the array
@@ -48,9 +46,9 @@ class Heap < CompleteNaryTree
48
46
  # * sift_down -- O(log n) on heap size
49
47
  #
50
48
  def pop
51
- node = @store.shift
52
- replacement = @store.pop
53
- @store.unshift replacement if replacement
49
+ node = @array.shift
50
+ replacement = @array.pop
51
+ @array.unshift replacement if replacement
54
52
  self.sift_down(0)
55
53
  node
56
54
  end
@@ -58,7 +56,7 @@ class Heap < CompleteNaryTree
58
56
  # * return what pop would return (avoid sifting)
59
57
  #
60
58
  def peek
61
- @store.first
59
+ @array.first
62
60
  end
63
61
 
64
62
  # * called recursively
@@ -69,7 +67,7 @@ class Heap < CompleteNaryTree
69
67
  return self if idx <= 0
70
68
  pidx = self.class.parent_idx(idx, @child_slots)
71
69
  if !self.heapish?(pidx, idx)
72
- @store[idx], @store[pidx] = @store[pidx], @store[idx] # swap
70
+ @array[idx], @array[pidx] = @array[pidx], @array[idx] # swap
73
71
  self.sift_up(pidx)
74
72
  end
75
73
  self
@@ -81,12 +79,12 @@ class Heap < CompleteNaryTree
81
79
  # * slower than sift_up because one parent vs multiple children
82
80
  #
83
81
  def sift_down(idx)
84
- return self if idx >= @store.size
82
+ return self if idx >= @array.size
85
83
  cidxs = self.class.children_idx(idx, @child_slots)
86
84
  # promote the heapiest child
87
85
  cidx = self.heapiest(cidxs)
88
86
  if !self.heapish?(idx, cidx)
89
- @store[idx], @store[cidx] = @store[cidx], @store[idx] # swap
87
+ @array[idx], @array[cidx] = @array[cidx], @array[idx] # swap
90
88
  self.sift_down(cidx)
91
89
  end
92
90
  self
@@ -95,7 +93,7 @@ class Heap < CompleteNaryTree
95
93
  # are values of parent and child (by index) in accordance with heap property?
96
94
  #
97
95
  def heapish?(pidx, cidx)
98
- (@store[pidx] <=> @store[cidx]) != (@cmp_val * -1)
96
+ (@array[pidx] <=> @array[cidx]) != (@cmp_val * -1)
99
97
  end
100
98
 
101
99
  # return the heapiest idx in cidxs
@@ -103,7 +101,7 @@ class Heap < CompleteNaryTree
103
101
  def heapiest(cidxs)
104
102
  idx = cidxs.first
105
103
  cidxs.each { |cidx|
106
- idx = cidx if cidx < @store.size and self.heapish?(cidx, idx)
104
+ idx = cidx if cidx < @array.size and self.heapish?(cidx, idx)
107
105
  }
108
106
  idx
109
107
  end
@@ -116,99 +114,7 @@ class Heap < CompleteNaryTree
116
114
  check_children = []
117
115
  self.class.children_idx(idx, @child_slots).each { |cidx|
118
116
  # cidx is arithmetically produced; the corresponding child may not exist
119
- if cidx < @store.size
120
- return false unless self.heapish?(idx, cidx)
121
- check_children << cidx
122
- end
123
- }
124
- check_children.each { |cidx| return false unless self.heap?(idx: cidx) }
125
- true
126
- end
127
- end
128
-
129
- #
130
- # LEGACY BELOW untouched for now
131
- #
132
-
133
- class BinaryHeap < CompleteBinaryTree
134
- # defaults to a MaxHeap, with the largest node at the root
135
- # specify a minheap with minheap: true or cmp_val: -1
136
- #
137
- def initialize(cmp_val: 1, minheap: false)
138
- super()
139
- cmp_val = -1 if minheap
140
- case cmp_val
141
- when -1, 1
142
- @cmp_val = cmp_val
143
- else
144
- raise(ArgumentError, "unknown comparison value: #{cmp_val}")
145
- end
146
- end
147
-
148
- # append to the array
149
- # sift_up -- O(log n) on heap size
150
- def push(node)
151
- @store << node
152
- self.sift_up(@store.size - 1)
153
- end
154
-
155
- # remove from the front of the array
156
- # move last node to root
157
- # sift_down -- O(log n) on heap size
158
- def pop
159
- node = @store.shift
160
- replacement = @store.pop
161
- @store.unshift replacement if replacement
162
- self.sift_down(0)
163
- node
164
- end
165
-
166
- # return what pop would return (avoid sifting)
167
- def peek
168
- @store.first
169
- end
170
-
171
- # called recursively
172
- # idx represents the node suspected to violate the heap
173
- # intended to be O(log n) on heap size
174
- def sift_up(idx)
175
- return self if idx <= 0
176
- pidx = self.class.parent_idx(idx)
177
- if !self.heapish?(pidx, idx)
178
- @store[idx], @store[pidx] = @store[pidx], @store[idx] # swap
179
- self.sift_up(pidx)
180
- end
181
- self
182
- end
183
-
184
- # called recursively
185
- # idx represents the node suspected to violate the heap
186
- # intended to be O(log n) on heap size
187
- def sift_down(idx)
188
- return self if idx >= @store.size
189
- lidx, ridx = self.class.children_idx(idx)
190
- # promote the heapiest child
191
- cidx = self.heapish?(lidx, ridx) ? lidx : ridx
192
- if !self.heapish?(idx, cidx)
193
- @store[idx], @store[cidx] = @store[cidx], @store[idx] # swap
194
- self.sift_down(cidx)
195
- end
196
- self
197
- end
198
-
199
- # are values of parent and child (by index) in accordance with heap property?
200
- def heapish?(pidx, cidx)
201
- (@store[pidx] <=> @store[cidx]) != (@cmp_val * -1)
202
- end
203
-
204
- # not used internally
205
- # checks that every node satisfies the heap property
206
- # calls heapish? on idx's children and then heap? on them recursively
207
- def heap?(idx: 0)
208
- check_children = []
209
- self.class.children_idx(idx).each { |cidx|
210
- # cidx is arithmetically produced; the corresponding child may not exist
211
- if cidx < @store.size
117
+ if cidx < @array.size
212
118
  return false unless self.heapish?(idx, cidx)
213
119
  check_children << cidx
214
120
  end