compsci 0.0.2.1 → 0.0.3.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: 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