compsci 0.0.3.1 → 0.1.0.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 +63 -14
- data/VERSION +1 -1
- data/compsci.gemspec +5 -22
- data/examples/binary_tree.rb +11 -6
- data/examples/complete_tree.rb +47 -0
- data/examples/heap.rb +19 -7
- data/examples/tree.rb +41 -0
- data/lib/compsci.rb +1 -2
- data/lib/compsci/complete_tree.rb +109 -0
- data/lib/compsci/fibonacci.rb +31 -30
- data/lib/compsci/fit.rb +135 -135
- data/lib/compsci/heap.rb +14 -108
- data/lib/compsci/names.rb +132 -0
- data/lib/compsci/node.rb +67 -0
- data/lib/compsci/timer.rb +30 -29
- data/lib/compsci/tree.rb +48 -138
- data/test/bench/tree.rb +2 -2
- data/test/complete_tree.rb +131 -0
- data/test/heap.rb +121 -34
- data/test/names.rb +96 -0
- data/test/node.rb +89 -0
- data/test/tree.rb +49 -237
- metadata +24 -2
@@ -0,0 +1,132 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module CompSci
|
3
|
+
module Names
|
4
|
+
WW1 = [:apples, :butter, :charlie, :duff, :edward, :freddy, :george,
|
5
|
+
:harry, :ink, :johnnie, :king, :london, :monkey, :nuts, :orange,
|
6
|
+
:pudding, :queenie, :robert, :sugar, :tommy, :uncle, :vinegar,
|
7
|
+
:willie, :xerxes, :yellow, :zebra]
|
8
|
+
WW2 = [:able, :baker, :charlie, :dog, :easy, :fox, :george, :how, :item,
|
9
|
+
:jig, :king, :love, :mike, :nan, :oboe, :peter, :queen, :roger,
|
10
|
+
:sugar, :tare, :uncle, :victor, :william, :xray, :yoke, :zebra]
|
11
|
+
NATO = [:alfa, :bravo, :charlie, :delta, :echo, :foxtrot, :golf, :hotel,
|
12
|
+
:india, :juliett, :kilo, :lima, :mike, :november, :oscar, :papa,
|
13
|
+
:quebec, :romeo, :sierra, :tango, :uniform, :victor, :whiskey,
|
14
|
+
:xray, :yankee, :zulu]
|
15
|
+
CRYPTO = [:alice, :bob, :charlie, :david, :eve, :frank, :grace, :heidi,
|
16
|
+
:judy, :mallory, :olivia, :peggy, :sybil, :trudy, :victor,
|
17
|
+
:wendy]
|
18
|
+
ENGLISH_UPPER = [*'A'..'Z']
|
19
|
+
ENGLISH_LOWER = [*'a'..'z']
|
20
|
+
|
21
|
+
PLANETS = [:mercury, :venus, :earth, :mars, :jupiter, :saturn, :uranus,
|
22
|
+
:neptune, :pluto]
|
23
|
+
|
24
|
+
SOLAR = [:mercury, :venus, :earth, :mars, :asteroid_belt, :jupiter,
|
25
|
+
:saturn, :uranus, :neptune, :kuiper_belt, :scattered_disk,
|
26
|
+
:heliosphere]
|
27
|
+
|
28
|
+
# map val to [0..names.length]
|
29
|
+
def self.assign(val, names)
|
30
|
+
case val
|
31
|
+
when String
|
32
|
+
if val.match %r{\A\d+\z}
|
33
|
+
pos = val.to_i
|
34
|
+
else
|
35
|
+
case names.size
|
36
|
+
when (1..26)
|
37
|
+
pos = val[0].upcase.ord - 'A'.ord
|
38
|
+
when (27..99)
|
39
|
+
pos = val[0].ord - 'A'.ord
|
40
|
+
pos -= 6 if pos > 26
|
41
|
+
else
|
42
|
+
raise "unexpected names.size: #{names.size}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
when Integer
|
46
|
+
pos = val
|
47
|
+
else
|
48
|
+
raise "unexpected val: #{val} (#{val.class})"
|
49
|
+
end
|
50
|
+
|
51
|
+
if pos < 0 or pos >= names.size
|
52
|
+
raise "val #{val} pos #{pos} outside of names range (#{names.size})"
|
53
|
+
end
|
54
|
+
names[pos]
|
55
|
+
end
|
56
|
+
|
57
|
+
module Greek
|
58
|
+
UPPER = [*'Α'..'Ρ', *'Σ'..'Ω']
|
59
|
+
LOWER = [*('α'..'ρ'), *('σ'..'ω')]
|
60
|
+
SYMBOLS = [:alpha, :beta, :gamma, :delta, :epsilon, :zeta, :eta, :theta,
|
61
|
+
:iota, :kappa, :lambda, :mu, :nu, :xi, :omicron, :pi, :rho,
|
62
|
+
:sigma, :tau, :upsilon, :phi, :chi, :psi, :omega]
|
63
|
+
|
64
|
+
# e.g. { alpha: ['Α', 'α'] ...
|
65
|
+
CHAR_MAP = {}
|
66
|
+
SYMBOLS.each_with_index { |sym, i| CHAR_MAP[sym] = [UPPER[i], LOWER[i]] }
|
67
|
+
|
68
|
+
LATIN_SYMBOLS = {
|
69
|
+
a: :alpha,
|
70
|
+
b: :beta,
|
71
|
+
c: :gamma,
|
72
|
+
d: :delta,
|
73
|
+
e: :epsilon,
|
74
|
+
f: :zeta,
|
75
|
+
g: :eta,
|
76
|
+
h: :theta,
|
77
|
+
i: :iota,
|
78
|
+
j: :xi, # use for j, q and w
|
79
|
+
k: :kappa,
|
80
|
+
l: :lambda,
|
81
|
+
m: :mu,
|
82
|
+
n: :nu,
|
83
|
+
# nonesuch: => :xi,
|
84
|
+
o: :omicron,
|
85
|
+
p: :pi,
|
86
|
+
q: :xi, # use for j, q and w
|
87
|
+
r: :rho,
|
88
|
+
s: :sigma,
|
89
|
+
t: :tau,
|
90
|
+
u: :upsilon,
|
91
|
+
v: :phi,
|
92
|
+
w: :xi, # use for j, q and w
|
93
|
+
x: :chi,
|
94
|
+
y: :psi,
|
95
|
+
z: :omega,
|
96
|
+
}
|
97
|
+
SYMBOLS26 = LATIN_SYMBOLS.values
|
98
|
+
|
99
|
+
def self.upper(latin_str)
|
100
|
+
CHAR_MAP.fetch(self.sym(latin_str)).first
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.lower(latin_str)
|
104
|
+
CHAR_MAP.fetch(self.sym(latin_str)).last
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.sym(val)
|
108
|
+
case val
|
109
|
+
when String
|
110
|
+
if val.match %r{\A\d+\z}
|
111
|
+
val = val.to_i
|
112
|
+
if val >= 0 and val < SYMBOLS.size
|
113
|
+
SYMBOLS[val]
|
114
|
+
else
|
115
|
+
raise "val #{val} not in range (#{SYMBOLS.size})"
|
116
|
+
end
|
117
|
+
else
|
118
|
+
LATIN_SYMBOLS.fetch val[0].downcase.to_sym
|
119
|
+
end
|
120
|
+
when Integer
|
121
|
+
if val >= 0 and val < SYMBOLS.size
|
122
|
+
SYMBOLS[val]
|
123
|
+
else
|
124
|
+
raise "val #{val} not in range (#{SYMBOLS.size})"
|
125
|
+
end
|
126
|
+
else
|
127
|
+
raise "unexpected val #{val} (#{val.class})"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
data/lib/compsci/node.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
module CompSci
|
2
|
+
# has a value and an array of children
|
3
|
+
class Node
|
4
|
+
attr_accessor :value
|
5
|
+
attr_reader :children
|
6
|
+
|
7
|
+
def initialize(value)
|
8
|
+
@value = value
|
9
|
+
@children = []
|
10
|
+
# @metadata = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_child(node)
|
14
|
+
@children << node
|
15
|
+
end
|
16
|
+
|
17
|
+
def new_child(value)
|
18
|
+
self.add_child self.class.new(value)
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_parent(node)
|
22
|
+
node.add_child(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
@value.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def inspect
|
30
|
+
"#<%s:0x%0xi @value=%s @children=[%s]>" %
|
31
|
+
[self.class,
|
32
|
+
self.object_id,
|
33
|
+
self.to_s,
|
34
|
+
@children.map(&:to_s).join(', ')]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# like Node but with a reference to its parent
|
39
|
+
class ChildNode < Node
|
40
|
+
attr_accessor :parent
|
41
|
+
|
42
|
+
def initialize(value)
|
43
|
+
@parent = nil
|
44
|
+
super(value)
|
45
|
+
end
|
46
|
+
|
47
|
+
# O(log n) recursive
|
48
|
+
def gen
|
49
|
+
@parent ? @parent.gen + 1 : 0
|
50
|
+
end
|
51
|
+
|
52
|
+
def siblings
|
53
|
+
@parent ? @parent.children : []
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_child(node)
|
57
|
+
node.parent ||= self
|
58
|
+
raise "node has a parent: #{node.parent}" if node.parent != self
|
59
|
+
super(node)
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_parent(node)
|
63
|
+
@parent = node
|
64
|
+
super(node)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/compsci/timer.rb
CHANGED
@@ -1,36 +1,37 @@
|
|
1
|
-
|
1
|
+
module CompSci
|
2
2
|
|
3
|
-
module
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
module Timer
|
4
|
+
# lifted from seattlerb/minitest
|
5
|
+
if defined? Process::CLOCK_MONOTONIC
|
6
|
+
def self.now
|
7
|
+
Process.clock_gettime Process::CLOCK_MONOTONIC
|
8
|
+
end
|
9
|
+
else
|
10
|
+
def self.now
|
11
|
+
Time.now
|
12
|
+
end
|
8
13
|
end
|
9
|
-
else
|
10
|
-
def self.now
|
11
|
-
Time.now
|
12
|
-
end
|
13
|
-
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
def self.since(t)
|
16
|
+
self.now - t
|
17
|
+
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
def self.elapsed(&work)
|
20
|
+
t = self.now
|
21
|
+
return yield, self.since(t)
|
22
|
+
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
24
|
+
def self.loop_avg(count: 999, seconds: 1, &work)
|
25
|
+
i = 0
|
26
|
+
start = self.now
|
27
|
+
val = nil
|
28
|
+
loop {
|
29
|
+
val = yield
|
30
|
+
i += 1
|
31
|
+
break if i >= count
|
32
|
+
break if self.since(start) > seconds
|
33
|
+
}
|
34
|
+
return val, self.since(start) / i.to_f
|
35
|
+
end
|
35
36
|
end
|
36
37
|
end
|
data/lib/compsci/tree.rb
CHANGED
@@ -1,67 +1,11 @@
|
|
1
|
-
require 'compsci'
|
1
|
+
require 'compsci/node'
|
2
2
|
|
3
3
|
module CompSci
|
4
|
-
class Node
|
5
|
-
attr_accessor :value
|
6
|
-
attr_reader :children
|
7
|
-
|
8
|
-
def initialize(value)
|
9
|
-
@value = value
|
10
|
-
@children = []
|
11
|
-
# @metadata = {}
|
12
|
-
end
|
13
|
-
|
14
|
-
def add_child(node)
|
15
|
-
@children << node
|
16
|
-
end
|
17
|
-
|
18
|
-
def new_child(value)
|
19
|
-
self.add_child self.class.new(value)
|
20
|
-
end
|
21
|
-
|
22
|
-
def add_parent(node)
|
23
|
-
node.add_child(self)
|
24
|
-
end
|
25
|
-
|
26
|
-
def to_s
|
27
|
-
@value.to_s
|
28
|
-
end
|
29
|
-
|
30
|
-
def inspect
|
31
|
-
"#<%s:0x%0xi @value=%s @children=[%s]>" %
|
32
|
-
[self.class,
|
33
|
-
self.object_id,
|
34
|
-
self.to_s,
|
35
|
-
@children.map(&:to_s).join(', ')]
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# like Node but with a reference to its parent
|
40
|
-
class ChildNode < Node
|
41
|
-
attr_accessor :parent
|
42
|
-
|
43
|
-
def initialize(value)
|
44
|
-
@parent = nil
|
45
|
-
super(value)
|
46
|
-
end
|
47
|
-
|
48
|
-
def add_child(node)
|
49
|
-
node.parent ||= self
|
50
|
-
raise "node has a parent: #{node.parent}" if node.parent != self
|
51
|
-
super(node)
|
52
|
-
end
|
53
|
-
|
54
|
-
def add_parent(node)
|
55
|
-
@parent = node
|
56
|
-
super(node)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
4
|
class Tree
|
61
5
|
attr_reader :root
|
62
6
|
|
63
|
-
def initialize(
|
64
|
-
@root =
|
7
|
+
def initialize(node_class, val)
|
8
|
+
@root = node_class.new val
|
65
9
|
end
|
66
10
|
|
67
11
|
def df_search(node: nil, &blk)
|
@@ -96,9 +40,8 @@ module CompSci
|
|
96
40
|
class NaryTree < Tree
|
97
41
|
attr_reader :child_slots
|
98
42
|
|
99
|
-
def initialize(
|
100
|
-
super(
|
101
|
-
raise "#{klass}#parent required" unless @root.respond_to? :parent
|
43
|
+
def initialize(node_class, val, child_slots:)
|
44
|
+
super(node_class, val)
|
102
45
|
@child_slots = child_slots
|
103
46
|
end
|
104
47
|
|
@@ -106,35 +49,58 @@ module CompSci
|
|
106
49
|
node.children.size < @child_slots
|
107
50
|
end
|
108
51
|
|
52
|
+
def open_sibling
|
53
|
+
# try siblings first, only possible with Node#parent
|
54
|
+
if @open_parent.respond_to?(:siblings)
|
55
|
+
@open_parent.siblings.find { |s| self.open_parent?(s) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
109
59
|
def open_parent
|
110
60
|
@open_parent ||= @root
|
111
61
|
return @open_parent if self.open_parent?(@open_parent)
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
# try siblings first
|
116
|
-
if @open_parent.parent
|
117
|
-
@open_parent.parent.children.each { |c|
|
118
|
-
return @open_parent = c if self.open_parent?(c)
|
119
|
-
}
|
120
|
-
end
|
62
|
+
open_sibling = self.open_sibling
|
63
|
+
return @open_parent = open_sibling if open_sibling
|
121
64
|
@open_parent = self.bf_search { |n| self.open_parent?(n) }
|
122
65
|
end
|
123
66
|
|
124
67
|
def push(value)
|
125
68
|
self.open_parent.new_child value
|
126
69
|
end
|
70
|
+
|
71
|
+
def display(width: nil)
|
72
|
+
str = ''
|
73
|
+
old_level = 0
|
74
|
+
width ||= @child_slots * 40
|
75
|
+
self.bf_search { |node|
|
76
|
+
raise "#{node.class} not yet supported" unless node.respond_to? :gen
|
77
|
+
level = node.gen
|
78
|
+
if old_level != level
|
79
|
+
str += "\n"
|
80
|
+
old_level = level
|
81
|
+
end
|
82
|
+
# center in block_width
|
83
|
+
slots = @child_slots**level
|
84
|
+
block_width = width / slots
|
85
|
+
val = node.to_s
|
86
|
+
space = [(block_width + val.size) / 2, val.size + 1].max
|
87
|
+
str += val.ljust(space, ' ').rjust(block_width, ' ')
|
88
|
+
false
|
89
|
+
}
|
90
|
+
str
|
91
|
+
end
|
92
|
+
alias_method :to_s, :display
|
127
93
|
end
|
128
94
|
|
129
95
|
class BinaryTree < NaryTree
|
130
|
-
def initialize(
|
131
|
-
super(
|
96
|
+
def initialize(node_class, val)
|
97
|
+
super(node_class, val, child_slots: 2)
|
132
98
|
end
|
133
99
|
|
134
|
-
def
|
100
|
+
def display(width: 80)
|
135
101
|
count = 0
|
136
102
|
str = ''
|
137
|
-
self.bf_search
|
103
|
+
self.bf_search { |n|
|
138
104
|
count += 1
|
139
105
|
level = Math.log(count, 2).floor
|
140
106
|
block_width = width / (2**level)
|
@@ -144,74 +110,18 @@ module CompSci
|
|
144
110
|
}
|
145
111
|
str
|
146
112
|
end
|
113
|
+
alias_method :to_s, :display
|
147
114
|
end
|
148
115
|
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
157
|
-
end
|
158
|
-
|
159
|
-
def self.children_idx(idx, n)
|
160
|
-
Array.new(n) { |i| n*idx + i + 1 }
|
161
|
-
end
|
162
|
-
|
163
|
-
attr_reader :store
|
164
|
-
|
165
|
-
def initialize(store: [], child_slots: 2)
|
166
|
-
@store = store
|
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
|
176
|
-
end
|
177
|
-
|
178
|
-
def size
|
179
|
-
@store.size
|
180
|
-
end
|
181
|
-
|
182
|
-
def last_idx
|
183
|
-
@store.size - 1 unless @store.empty?
|
116
|
+
class TernaryTree < NaryTree
|
117
|
+
def initialize(node_class, val)
|
118
|
+
super(node_class, val, child_slots: 3)
|
184
119
|
end
|
185
120
|
end
|
186
121
|
|
187
|
-
|
188
|
-
|
189
|
-
|
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
|
122
|
+
class QuaternaryTree < NaryTree
|
123
|
+
def initialize(node_class, val)
|
124
|
+
super(node_class, val, child_slots: 4)
|
215
125
|
end
|
216
126
|
end
|
217
127
|
end
|