compsci 0.0.2.1 → 0.0.3.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: 4ee20715906280e4a366f493195ae4decf079cf1
4
- data.tar.gz: '0088708c15ca4dae9cdc58ef35d1f85dd60203cc'
3
+ metadata.gz: cc2eff6c2450cd9807ab097dd86eebb37420a7b7
4
+ data.tar.gz: 470311b19fc70c7ab1a5d405c3aba939f57464e7
5
5
  SHA512:
6
- metadata.gz: 38071308dbc5a6e1988abf8671166c10b793f4baa9bb7371c1fe65c3d2570f1a38caaf61042fa247d58a0451b8711b1ec0eb0ea98e67bfe9fab8bbd8c0652318
7
- data.tar.gz: 11a04a2484e15b29a049c1d6c48c373e2360c98b24f02d4bc874353aa29917acbf53acc1932b1d3629796afd5956c91048a14ff8c46b54904f457d1588e7aa45
6
+ metadata.gz: 37214ed2f4c2fd3f8837f1908fc78b07be79e624ba7f387f40c68aec49f93b1b866589fab0b863e02e6add2d6dcbfe4508c9ae1bdf1864269427dce25fdb15b4
7
+ data.tar.gz: 52dc0115e1f44a769da4dc3f1e721624e3963cef0111d098af6ad6beb72728b120905aaaeb45161950c79455810bbf8caab5426fb22aee716f5e7e92f84244a4
data/README.md CHANGED
@@ -34,11 +34,55 @@ second, constant up past 1M pushes.
34
34
 
35
35
  ## [`Timer`](/lib/compsci/timer.rb) functions
36
36
 
37
- * `Timer.now` - uses `Process::CLOCK_MONOTONIC` if available
38
- * `Timer.since` - provides the elapsed time since a prior time
39
- * `Timer.elapsed` - provides the elapsed time to run a block
40
- * `Timer.loop_average` - runs a block repeatedly and provides the mean elapsed
41
- time
37
+ * `Timer.now` - uses `Process::CLOCK_MONOTONIC` if available
38
+ * `Timer.since` - provides the elapsed time since a prior time
39
+ * `Timer.elapsed` - provides the elapsed time to run a block
40
+ * `Timer.loop_avg` - loops a block; returns final value and mean elapsed time
41
+
42
+ ```ruby
43
+ require 'compsci/timer'
44
+
45
+ include CompSci
46
+
47
+ overall_start = Timer.now
48
+
49
+ start = Timer.now
50
+ print "running sleep 0.01 (50x): "
51
+ _answer, each_et = Timer.loop_avg(count: 50) {
52
+ print '.'
53
+ sleep 0.01
54
+ }
55
+ puts
56
+ puts "each: %0.3f" % each_et
57
+ puts "elapsed: %0.3f" % Timer.since(start)
58
+ puts "cumulative: %0.3f" % Timer.since(overall_start)
59
+ puts
60
+
61
+
62
+ start = Timer.now
63
+ print "running sleep 0.02 (0.3 s): "
64
+ _answer, each_et = Timer.loop_avg(seconds: 0.3) {
65
+ print '.'
66
+ sleep 0.02
67
+ }
68
+ puts
69
+ puts "each: %0.3f" % each_et
70
+ puts "elapsed: %0.3f" % Timer.since(start)
71
+ puts "cumulative: %0.3f" % Timer.since(overall_start)
72
+ puts
73
+ ```
74
+
75
+ ```
76
+ running sleep 0.01 (50x): ..................................................
77
+ each: 0.010
78
+ elapsed: 0.524
79
+ cumulative: 0.524
80
+
81
+ running sleep 0.02 (0.3 s): ...............
82
+ each: 0.020
83
+ elapsed: 0.304
84
+ cumulative: 0.828
85
+ ```
42
86
 
43
87
  ## [`Fit`](lib/compsci/fit.rb) functions
44
88
 
data/Rakefile CHANGED
@@ -31,7 +31,7 @@ metrics_tasks = []
31
31
  begin
32
32
  require 'flog_task'
33
33
  FlogTask.new do |t|
34
- t.threshold = 450
34
+ t.threshold = 600
35
35
  t.dirs = ['lib']
36
36
  t.verbose = true
37
37
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2.1
1
+ 0.0.3.1
data/compsci.gemspec CHANGED
@@ -23,7 +23,6 @@ Gem::Specification.new do |s|
23
23
  lib/compsci/tree.rb
24
24
  examples/binary_tree.rb
25
25
  examples/heap.rb
26
- examples/timer.rb
27
26
  test/fibonacci.rb
28
27
  test/fit.rb
29
28
  test/heap.rb
data/examples/heap.rb CHANGED
@@ -13,7 +13,7 @@ EOF
13
13
  count = 0
14
14
  start = Timer.now
15
15
  start_100k = Timer.now
16
- h = Heap.new
16
+ h = BinaryHeap.new
17
17
 
18
18
  loop {
19
19
  count += 1
data/lib/compsci/heap.rb CHANGED
@@ -1,27 +1,136 @@
1
1
  require 'compsci/tree'
2
2
 
3
- # A Heap is a partially sorted, complete binary tree with the property:
4
- # * Every node has a value larger (or smaller) than that of its children.
5
- #
6
- # This class implements a heap using a simple array for storage.
7
- # Array index math is used to find:
8
- # * The root node (idx 0)
9
- # * The "bottom-most" leaf node (last idx)
10
- # * Parent idx (idx-1 / 2)
11
- # * Child idx (2*idx + 1, 2*idx + 2)
3
+ include CompSci
4
+
5
+ # A Heap is a partially sorted, complete N-ary tree with the property:
6
+ # * Every node has a value larger (or smaller) than that of its children
7
+ # (the heap property is satisfied when a parent value equals a child value)
12
8
  #
13
- # Any Comparable may be used for node values.
14
- # Initialize a heap with a cmp_val, either 1 for a MaxHeap or -1 for a MinHeap.
15
- # The heap property is satisfied when a parent value equals a child value.
16
- # Insertion (push) and removal (pop) are O(log n) where n is the heap size.
17
- # Nodes are inserted at the end of the array, and sift_up is called to
9
+ # Implementation details:
10
+ # * Any Comparable may be used for node values.
11
+ # * Initialize a heap with a cmp_val, either 1 for a MaxHeap or -1 for a
12
+ # MinHeap.
13
+ # * Insertion (push) and removal (pop) are O(logb n) where n is the heap size
14
+ # and b is child_slots (the N in N-ary)
15
+ # * Nodes are inserted at the end of the array, and sift_up is called to
18
16
  # reestablish the heap property.
19
- # Nodes are removed from the start of the array, and sift_down is called to
17
+ # * Nodes are removed from the start of the array, and sift_down is called to
20
18
  # reestablish the heap property.
21
- # Sift_up and sift_down are O(log n) because they only have to check and swap
22
- # nodes at each layer of the tree, and there are log n layers to the tree.
19
+ # * Sift_up and sift_down are O(logb n) because they only have to check and
20
+ # swap nodes at each layer of the tree, and there are log(n, base b) layers
21
+ # to the tree.
22
+ #
23
+ class Heap < CompleteNaryTree
24
+ # * defaults to a MaxHeap, with the largest node at the root
25
+ # * specify a minheap with minheap: true or cmp_val: -1
26
+ #
27
+ def initialize(cmp_val: 1, minheap: false, child_slots: 2)
28
+ super(child_slots: child_slots)
29
+ cmp_val = -1 if minheap
30
+ case cmp_val
31
+ when -1, 1
32
+ @cmp_val = cmp_val
33
+ else
34
+ raise(ArgumentError, "unknown comparison value: #{cmp_val}")
35
+ end
36
+ end
37
+
38
+ # * append to the array
39
+ # * sift_up -- O(log n) on heap size
40
+ #
41
+ def push(node)
42
+ @store << node
43
+ self.sift_up(@store.size - 1)
44
+ end
45
+
46
+ # * remove from the front of the array
47
+ # * move last node to root
48
+ # * sift_down -- O(log n) on heap size
49
+ #
50
+ def pop
51
+ node = @store.shift
52
+ replacement = @store.pop
53
+ @store.unshift replacement if replacement
54
+ self.sift_down(0)
55
+ node
56
+ end
57
+
58
+ # * return what pop would return (avoid sifting)
59
+ #
60
+ def peek
61
+ @store.first
62
+ end
63
+
64
+ # * called recursively
65
+ # * idx represents the node suspected to violate the heap
66
+ # * intended to be O(log n) on heap size (log base child_slots)
67
+ #
68
+ def sift_up(idx)
69
+ return self if idx <= 0
70
+ pidx = self.class.parent_idx(idx, @child_slots)
71
+ if !self.heapish?(pidx, idx)
72
+ @store[idx], @store[pidx] = @store[pidx], @store[idx] # swap
73
+ self.sift_up(pidx)
74
+ end
75
+ self
76
+ end
77
+
78
+ # * called recursively
79
+ # * idx represents the node suspected to violate the heap
80
+ # * intended to be O(log n) on heap size (log base child_slots)
81
+ # * slower than sift_up because one parent vs multiple children
82
+ #
83
+ def sift_down(idx)
84
+ return self if idx >= @store.size
85
+ cidxs = self.class.children_idx(idx, @child_slots)
86
+ # promote the heapiest child
87
+ cidx = self.heapiest(cidxs)
88
+ if !self.heapish?(idx, cidx)
89
+ @store[idx], @store[cidx] = @store[cidx], @store[idx] # swap
90
+ self.sift_down(cidx)
91
+ end
92
+ self
93
+ end
94
+
95
+ # are values of parent and child (by index) in accordance with heap property?
96
+ #
97
+ def heapish?(pidx, cidx)
98
+ (@store[pidx] <=> @store[cidx]) != (@cmp_val * -1)
99
+ end
100
+
101
+ # return the heapiest idx in cidxs
102
+ #
103
+ def heapiest(cidxs)
104
+ idx = cidxs.first
105
+ cidxs.each { |cidx|
106
+ idx = cidx if cidx < @store.size and self.heapish?(cidx, idx)
107
+ }
108
+ idx
109
+ end
110
+
111
+ # * not used internally
112
+ # * checks that every node satisfies the heap property
113
+ # * calls heapish? on idx's children and then heap? on them recursively
114
+ #
115
+ def heap?(idx: 0)
116
+ check_children = []
117
+ self.class.children_idx(idx, @child_slots).each { |cidx|
118
+ # 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
+
23
129
  #
24
- class CompSci::Heap < CompSci::CompleteBinaryTree
130
+ # LEGACY BELOW untouched for now
131
+ #
132
+
133
+ class BinaryHeap < CompleteBinaryTree
25
134
  # defaults to a MaxHeap, with the largest node at the root
26
135
  # specify a minheap with minheap: true or cmp_val: -1
27
136
  #
data/lib/compsci/timer.rb CHANGED
@@ -21,7 +21,7 @@ module CompSci::Timer
21
21
  return yield, self.since(t)
22
22
  end
23
23
 
24
- def self.loop_average(count: 999, seconds: 1, &work)
24
+ def self.loop_avg(count: 999, seconds: 1, &work)
25
25
  i = 0
26
26
  start = self.now
27
27
  val = nil
data/lib/compsci/tree.rb CHANGED
@@ -149,21 +149,30 @@ module CompSci
149
149
  # A CompleteBinaryTree can very efficiently use an array for storage using
150
150
  # simple arithmetic to determine parent child relationships.
151
151
  #
152
- class CompleteBinaryTree
153
- # integer math says idx 2 and idx 1 both have parent at idx 0
154
- def self.parent_idx(idx)
155
- (idx-1) / 2
152
+ # Likewise, we should be able to use an array for N children
153
+ #
154
+ class CompleteNaryTree
155
+ def self.parent_idx(idx, n)
156
+ (idx-1) / n
156
157
  end
157
158
 
158
- def self.children_idx(idx)
159
- [2*idx + 1, 2*idx + 2]
159
+ def self.children_idx(idx, n)
160
+ Array.new(n) { |i| n*idx + i + 1 }
160
161
  end
161
162
 
162
163
  attr_reader :store
163
164
 
164
- def initialize(store: [])
165
+ def initialize(store: [], child_slots: 2)
165
166
  @store = store
166
- # yield self if block_given?
167
+ @child_slots = child_slots
168
+ end
169
+
170
+ def push node
171
+ @store.push node
172
+ end
173
+
174
+ def pop
175
+ @store.pop
167
176
  end
168
177
 
169
178
  def size
@@ -174,4 +183,35 @@ module CompSci
174
183
  @store.size - 1 unless @store.empty?
175
184
  end
176
185
  end
186
+
187
+ # Heap still expects CompleteBinaryTree
188
+ #
189
+ class CompleteBinaryTree < CompleteNaryTree
190
+ # integer math says idx 2 and idx 1 both have parent at idx 0
191
+ def self.parent_idx(idx)
192
+ (idx-1) / 2
193
+ end
194
+
195
+ def self.children_idx(idx)
196
+ [2*idx + 1, 2*idx + 2]
197
+ end
198
+
199
+ attr_reader :store
200
+
201
+ def initialize(store: [])
202
+ super(store: store, child_slots: 2)
203
+ end
204
+
205
+ # TODO: generalize for N != 2
206
+ def to_s(node: nil, width: 80)
207
+ str = ''
208
+ @store.each_with_index { |n, i|
209
+ level = Math.log(i+1, 2).floor
210
+ block_width = width / (2**level)
211
+ str += "\n" if 2**level == i+1 and i > 0
212
+ str += n.to_s.ljust(block_width / 2, ' ').rjust(block_width, ' ')
213
+ }
214
+ str
215
+ end
216
+ end
177
217
  end
data/test/timer.rb CHANGED
@@ -20,10 +20,10 @@ describe Timer do
20
20
  end
21
21
  end
22
22
 
23
- describe "loop_average" do
23
+ describe "loop_avg" 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.1) {
26
+ answer, avg_et = Timer.loop_avg(seconds: 0.1) {
27
27
  sleep 0.01
28
28
  :foo
29
29
  }
@@ -34,12 +34,21 @@ describe Timer do
34
34
 
35
35
  it "must repeat short loops and stop on time" do
36
36
  # see above, Timer.since(start)
37
- true.must_equal true
37
+ true
38
+ end
39
+
40
+ it "must cease looping after 5 loops" do
41
+ start = Timer.now
42
+ _answer, avg_et = Timer.loop_avg(count: 5) {
43
+ sleep 0.01
44
+ }
45
+ avg_et.must_be_close_to 0.015, 0.005
46
+ Timer.since(start).must_be_close_to 0.1, 0.05
38
47
  end
39
48
 
40
49
  it "must not interrupt long loops" do
41
50
  start = Timer.now
42
- _answer, avg_et = Timer.loop_average(seconds: 0.01) {
51
+ _answer, avg_et = Timer.loop_avg(seconds: 0.01) {
43
52
  sleep 0.1
44
53
  }
45
54
  Timer.since(start).must_be_close_to avg_et, 0.05
data/test/tree.rb CHANGED
@@ -293,3 +293,72 @@ describe CompleteBinaryTree do
293
293
  end
294
294
  end
295
295
  end
296
+
297
+ describe CompleteNaryTree do
298
+ it "must calculate a parent index for N=3" do
299
+ valid = {
300
+ 1 => 0,
301
+ 2 => 0,
302
+ 3 => 0,
303
+ 4 => 1,
304
+ 5 => 1,
305
+ 6 => 1,
306
+ 7 => 2,
307
+ 8 => 2,
308
+ 9 => 2,
309
+ 10 => 3,
310
+ }
311
+
312
+ invalid = {
313
+ 0 => -1,
314
+ -1 => -1,
315
+ -2 => -1,
316
+ }
317
+ valid.each { |idx, pidx|
318
+ CompleteNaryTree.parent_idx(idx, 3).must_equal pidx
319
+ }
320
+ invalid.each { |idx, pidx|
321
+ CompleteNaryTree.parent_idx(idx, 3).must_equal pidx
322
+ }
323
+ end
324
+
325
+ it "must calculate children indices for N=3" do
326
+ valid = {
327
+ 0 => [1, 2, 3],
328
+ 1 => [4, 5, 6],
329
+ 2 => [7, 8, 9],
330
+ 3 => [10, 11, 12],
331
+ }
332
+
333
+ invalid = {
334
+ -3 => [-8, -7, -6],
335
+ -2 => [-5, -4, -3],
336
+ -1 => [-2, -1, 0],
337
+ }
338
+
339
+ valid.each { |idx, cidx|
340
+ CompleteNaryTree.children_idx(idx, 3).must_equal cidx
341
+ }
342
+ invalid.each { |idx, cidx|
343
+ CompleteNaryTree.children_idx(idx, 3).must_equal cidx
344
+ }
345
+ end
346
+
347
+ describe "instance" do
348
+ before do
349
+ @array = (0..99).sort_by { rand }
350
+ @empty = CompleteNaryTree.new(child_slots: 5)
351
+ @nonempty = CompleteNaryTree.new(store: @array, child_slots: 3)
352
+ end
353
+
354
+ it "must have a size" do
355
+ @empty.size.must_equal 0
356
+ @nonempty.size.must_equal @array.size
357
+ end
358
+
359
+ it "must have a last_idx, nil when empty" do
360
+ @empty.last_idx.nil?.must_equal true
361
+ @nonempty.last_idx.must_equal 99
362
+ end
363
+ end
364
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: compsci
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2.1
4
+ version: 0.0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rick Hull
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-26 00:00:00.000000000 Z
11
+ date: 2017-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -36,7 +36,6 @@ files:
36
36
  - compsci.gemspec
37
37
  - examples/binary_tree.rb
38
38
  - examples/heap.rb
39
- - examples/timer.rb
40
39
  - lib/compsci.rb
41
40
  - lib/compsci/fibonacci.rb
42
41
  - lib/compsci/fit.rb
data/examples/timer.rb DELETED
@@ -1,30 +0,0 @@
1
- require 'compsci/timer'
2
-
3
- include CompSci
4
-
5
- overall_start = Timer.now
6
-
7
- start = Timer.now
8
- print "running sleep 0.01 (50x): "
9
- _answer, each_et = Timer.loop_average(count: 50) {
10
- print '.'
11
- sleep 0.01
12
- }
13
- puts
14
- puts "each: %0.3f" % each_et
15
- puts "elapsed: %0.3f" % Timer.since(start)
16
- puts "cumulative: %0.3f" % Timer.since(overall_start)
17
- puts
18
-
19
-
20
- start = Timer.now
21
- print "running sleep 0.02 (0.3 s): "
22
- _answer, each_et = Timer.loop_average(seconds: 0.3) {
23
- print '.'
24
- sleep 0.02
25
- }
26
- puts
27
- puts "each: %0.3f" % each_et
28
- puts "elapsed: %0.3f" % Timer.since(start)
29
- puts "cumulative: %0.3f" % Timer.since(overall_start)
30
- puts