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 +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
|