compsci 0.0.3.1 → 0.1.0.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.
@@ -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
@@ -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
@@ -1,36 +1,37 @@
1
- require 'compsci'
1
+ module CompSci
2
2
 
3
- module CompSci::Timer
4
- # lifted from seattlerb/minitest
5
- if defined? Process::CLOCK_MONOTONIC
6
- def self.now
7
- Process.clock_gettime Process::CLOCK_MONOTONIC
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
- def self.since(t)
16
- self.now - t
17
- end
15
+ def self.since(t)
16
+ self.now - t
17
+ end
18
18
 
19
- def self.elapsed(&work)
20
- t = self.now
21
- return yield, self.since(t)
22
- end
19
+ def self.elapsed(&work)
20
+ t = self.now
21
+ return yield, self.since(t)
22
+ end
23
23
 
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
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
@@ -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(klass, val)
64
- @root = klass.new val
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(klass, val, child_slots:)
100
- super(klass, val)
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
- # TODO: ugh, there must be a better way, this is O(n)
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(klass, val)
131
- super(klass, val, child_slots: 2)
96
+ def initialize(node_class, val)
97
+ super(node_class, val, child_slots: 2)
132
98
  end
133
99
 
134
- def to_s(node: nil, width: 80)
100
+ def display(width: 80)
135
101
  count = 0
136
102
  str = ''
137
- self.bf_search(node: node) { |n|
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
- # A CompleteBinaryTree can very efficiently use an array for storage using
150
- # simple arithmetic to determine parent child relationships.
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
- # 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
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