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 +4 -4
- data/README.md +49 -5
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/compsci.gemspec +0 -1
- data/examples/heap.rb +1 -1
- data/lib/compsci/heap.rb +127 -18
- data/lib/compsci/timer.rb +1 -1
- data/lib/compsci/tree.rb +48 -8
- data/test/timer.rb +13 -4
- data/test/tree.rb +69 -0
- metadata +2 -3
- data/examples/timer.rb +0 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cc2eff6c2450cd9807ab097dd86eebb37420a7b7
|
4
|
+
data.tar.gz: 470311b19fc70c7ab1a5d405c3aba939f57464e7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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`
|
38
|
-
* `Timer.since`
|
39
|
-
* `Timer.elapsed`
|
40
|
-
* `Timer.
|
41
|
-
|
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
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.3.1
|
data/compsci.gemspec
CHANGED
data/examples/heap.rb
CHANGED
data/lib/compsci/heap.rb
CHANGED
@@ -1,27 +1,136 @@
|
|
1
1
|
require 'compsci/tree'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
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
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
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(
|
22
|
-
# nodes at each layer of the tree, and there are log
|
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
|
-
|
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
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
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
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
|
-
|
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
|
-
|
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 "
|
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.
|
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
|
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.
|
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.
|
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-
|
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
|