compsci 0.0.3.1 → 0.1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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