compsci 0.3.0.1 → 0.3.1.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.
@@ -1,11 +1,11 @@
1
1
  require 'compsci/heap'
2
- require 'compsci/timer'
3
2
 
4
3
  include CompSci
5
4
 
6
5
  puts <<EOF
6
+
7
7
  #
8
- # display the results of TernaryHeap push and pop
8
+ # display the results of ternary Heap push and pop
9
9
  #
10
10
 
11
11
  EOF
@@ -38,3 +38,41 @@ puts "array: #{h.array.inspect}"
38
38
  puts "heap: #{h.heap?}"
39
39
  puts h
40
40
  puts
41
+
42
+
43
+ puts <<EOF
44
+
45
+ #
46
+ # display the results of binary Heap push and pop
47
+ #
48
+
49
+ EOF
50
+
51
+ h = Heap.new(child_slots: 2)
52
+
53
+ puts "push: %s" % Array.new(30) { rand(99).tap { |i| h.push i } }.join(' ')
54
+ puts "array: #{h.array.inspect}"
55
+ puts "heap: #{h.heap?}"
56
+ puts h
57
+ puts
58
+ puts
59
+
60
+ puts "pop: %i" % h.pop
61
+ puts "array: #{h.array.inspect}"
62
+ puts "heap: #{h.heap?}"
63
+ puts h
64
+ puts
65
+ puts
66
+
67
+ puts "pop: %s" % Array.new(9) { h.pop }.join(' ')
68
+ puts "array: #{h.array.inspect}"
69
+ puts "heap: #{h.heap?}"
70
+ puts h
71
+ puts
72
+ puts
73
+
74
+ puts "push: %s" % Array.new(30) { rand(99).tap { |i| h.push i } }.join(' ')
75
+ puts "array: #{h.array.inspect}"
76
+ puts "heap: #{h.heap?}"
77
+ puts h
78
+ puts
@@ -6,12 +6,18 @@ include CompSci
6
6
  runtime = (ARGV.shift || "3").to_i
7
7
 
8
8
  puts <<EOF
9
+
9
10
  #
10
- # 3 seconds worth of pushes
11
+ # #{runtime} seconds worth of Heap pushes
11
12
  #
12
13
 
13
14
  EOF
14
15
 
16
+
17
+ # pregenerate a sequence of random numbers
18
+ # every NUMBERWANGth request, shift the sequence and push a new random
19
+ # this should mitigate random number generation from interfering with timing
20
+ # while also mitigating any chance of cyclic behavior
15
21
  RANDMAX = 1_000
16
22
  NUMBERWANG = 1_000
17
23
  NUMS = (0..(RANDMAX - 1)).to_a.shuffle
@@ -0,0 +1,30 @@
1
+ require 'compsci/node'
2
+ require 'compsci/names'
3
+
4
+ include CompSci
5
+
6
+ randmax = (ARGV.shift || 50).to_i
7
+
8
+ puts <<EOF
9
+
10
+ #
11
+ # Insert #{randmax} nodes into a ternary search tree (random keys)
12
+ #
13
+
14
+ EOF
15
+
16
+ root = KeyNode.new(rand(randmax), key: rand(randmax), children: 3)
17
+ randmax.times { puts root.insert(rand(randmax), rand(randmax)) }
18
+ puts root.display
19
+
20
+ puts <<EOF
21
+
22
+ #
23
+ # Search for #{randmax} keys in order
24
+ #
25
+
26
+ EOF
27
+
28
+ randmax.times { |key|
29
+ puts "search #{key}: #{root.search(key).map { |n| n.value }.join(' ')}"
30
+ }
@@ -1,42 +1,44 @@
1
1
  require 'compsci/node'
2
- require 'compsci/tree'
3
- require 'compsci/timer'
4
2
 
5
3
  include CompSci
6
4
 
7
5
  puts <<EOF
6
+
8
7
  #
9
- # Try out Binary-, Ternary-, and QuaternaryTree
8
+ # Fill up and display some trees
10
9
  #
11
10
 
12
11
  EOF
13
12
 
14
13
  vals = Array.new(30) { rand 99 }
14
+ puts "vals: #{vals.inspect}"
15
15
 
16
- [BinaryTree, TernaryTree, QuaternaryTree].each { |tree_class|
17
- # start with the same vals for each class
16
+ [2, 3, 4].each { |children|
18
17
  my_vals = vals.dup
19
- p my_vals
20
- tree = tree_class.new(ChildFlexNode, my_vals.shift)
21
- tree.push my_vals.shift until my_vals.empty?
22
- p tree
23
- puts tree.display(width: 80)
24
- puts
25
- visited = []
26
- tree.df_search { |n|
27
- visited << n
28
- false # or n.value > 90
29
- }
30
- puts "df_search visited: %s" % visited.join(' ')
31
- puts
32
- puts
33
18
 
34
- # push different vals for each class
35
- my_vals = Array.new(30) { rand 99 }
36
- puts "push: #{my_vals.inspect}"
37
- tree.push my_vals.shift until my_vals.empty?
38
- puts
39
- puts tree.display(width: 80)
19
+ puts <<EOF
20
+
21
+ #
22
+ # Children: #{children}
23
+ #
24
+
25
+ EOF
26
+
27
+
28
+ root = Node.new my_vals.shift, children: children
29
+ nodes = [root]
30
+ until my_vals.empty?
31
+ new_nodes = []
32
+ nodes.each { |node|
33
+ children.times { |i|
34
+ node[i] = Node.new my_vals.shift, children: children
35
+ }
36
+ new_nodes += node.children
37
+ }
38
+ nodes = new_nodes
39
+ end
40
+ p root
40
41
  puts
42
+ puts root.display(width: 80)
41
43
  puts
42
44
  }
@@ -1 +1,10 @@
1
- module CompSci; end
1
+ module CompSci
2
+ # thanks apeiros
3
+ # https://gist.github.com/rickhull/d0b579aa08c85430b0dc82a791ff12d6
4
+ def self.power_of?(num, base)
5
+ return false if base <= 1
6
+ mod = 0
7
+ num, mod = num.divmod(base) until num == 1 || mod > 0
8
+ mod == 0
9
+ end
10
+ end
@@ -1,10 +1,8 @@
1
1
  module CompSci
2
- # A CompleteNaryTree can very efficiently use an array for storage using
2
+ # A CompleteTree can very efficiently use an array for storage using
3
3
  # simple arithmetic to determine parent child relationships.
4
4
  #
5
- # It is kept separate from compsci/tree as it does not require compsci/node
6
- #
7
- class CompleteNaryTree
5
+ class CompleteTree
8
6
  # integer math maps several children to one parent
9
7
  def self.parent_idx(idx, n)
10
8
  (idx-1) / n
@@ -67,6 +65,7 @@ module CompSci
67
65
  puts "not yet"
68
66
  end
69
67
 
68
+ # TODO: fixme
70
69
  def display(width: 80)
71
70
  str = ''
72
71
  old_level = 0
@@ -89,19 +88,19 @@ module CompSci
89
88
  alias_method :to_s, :display
90
89
  end
91
90
 
92
- class CompleteBinaryTree < CompleteNaryTree
91
+ class CompleteBinaryTree < CompleteTree
93
92
  def initialize(array: [])
94
93
  super(array: array, child_slots: 2)
95
94
  end
96
95
  end
97
96
 
98
- class CompleteTernaryTree < CompleteNaryTree
97
+ class CompleteTernaryTree < CompleteTree
99
98
  def initialize(array: [])
100
99
  super(array: array, child_slots: 3)
101
100
  end
102
101
  end
103
102
 
104
- class CompleteQuaternaryTree < CompleteNaryTree
103
+ class CompleteQuaternaryTree < CompleteTree
105
104
  def initialize(array: [])
106
105
  super(array: array, child_slots: 4)
107
106
  end
@@ -0,0 +1,90 @@
1
+ require 'compsci/node'
2
+
3
+ module CompSci
4
+ #
5
+ # FlexNodes accumulate children; no child gaps
6
+ #
7
+
8
+ # FlexNode API is #add_child, #add_parent, #new_child
9
+
10
+ class FlexNode < Node
11
+ def initialize(value, metadata: {})
12
+ @value = value
13
+ @children = []
14
+ @metadata = metadata
15
+ end
16
+
17
+ def add_child(node)
18
+ @children << node
19
+ end
20
+
21
+ def new_child(value)
22
+ self.add_child self.class.new(value)
23
+ end
24
+
25
+ def add_parent(node)
26
+ node.add_child(self)
27
+ end
28
+
29
+ def df_search(&blk)
30
+ return self if yield self
31
+ stop_node = nil
32
+ @children.each { |child|
33
+ stop_node = child.df_search(&blk)
34
+ break if stop_node
35
+ }
36
+ stop_node
37
+ end
38
+
39
+ def bf_search(&blk)
40
+ destinations = [self]
41
+ while !destinations.empty?
42
+ node = destinations.shift
43
+ return node if yield node
44
+ destinations += node.children
45
+ end
46
+ nil
47
+ end
48
+
49
+ def open_parent?(child_slots)
50
+ @children.size < child_slots
51
+ end
52
+
53
+ def open_parent(child_slots)
54
+ self.bf_search { |n| n.open_parent?(child_slots) }
55
+ end
56
+
57
+ def push(value, child_slots)
58
+ self.open_parent(child_slots).new_child value
59
+ end
60
+ end
61
+
62
+ class ChildFlexNode < FlexNode
63
+ attr_accessor :parent
64
+
65
+ def initialize(value, metadata: {})
66
+ super(value, metadata: metadata)
67
+ @parent = nil
68
+ end
69
+
70
+ # O(log n) recursive
71
+ def gen
72
+ @parent ? @parent.gen + 1 : 0
73
+ end
74
+
75
+ def siblings
76
+ @parent ? @parent.children : []
77
+ end
78
+
79
+ def add_child(node)
80
+ node.parent ||= self
81
+ raise "node has a parent: #{node.parent}" if node.parent != self
82
+ @children << node
83
+ end
84
+
85
+ def add_parent(node)
86
+ @parent = node
87
+ @parent.add_child(self)
88
+ end
89
+ end
90
+ end
@@ -18,7 +18,7 @@ require 'compsci/complete_tree'
18
18
  # swap nodes at each layer of the tree, and there are log(n, base b) layers
19
19
  # to the tree.
20
20
  #
21
- class CompSci::Heap < CompSci::CompleteNaryTree
21
+ class CompSci::Heap < CompSci::CompleteTree
22
22
  # * defaults to a MaxHeap, with the largest node at the root
23
23
  # * specify a minheap with minheap: true or cmp_val: -1
24
24
  #
@@ -65,7 +65,6 @@ class CompSci::Heap < CompSci::CompleteNaryTree
65
65
  #
66
66
  def sift_up(idx)
67
67
  return self if idx <= 0
68
- # print '.'
69
68
  pidx = self.class.parent_idx(idx, @child_slots)
70
69
  if !self.heapish?(pidx, idx)
71
70
  @array[idx], @array[pidx] = @array[pidx], @array[idx] # swap
@@ -0,0 +1,62 @@
1
+ require 'compsci/names'
2
+
3
+ module CompSci::Names::Pokemon
4
+ DATAFILE = File.join(__dir__, 'pokemon.txt')
5
+
6
+ def self.array
7
+ @@array ||= self.read_file
8
+ @@array
9
+ end
10
+
11
+ def self.hash
12
+ @@hash ||= self.read_hash
13
+ @@hash
14
+ end
15
+
16
+ # an array of all the names
17
+ def self.read_file(path: DATAFILE)
18
+ File.readlines(path).map { |n| n.chomp }
19
+ end
20
+
21
+ # return an array, possibly empty, if all: true
22
+ # return a string, possibly nil, if all: false
23
+ def self.grep(rgx, path: DATAFILE, all: false)
24
+ ary = []
25
+ File.open(path).each_line { |l|
26
+ if l.match rgx
27
+ ary << l.chomp
28
+ break unless all
29
+ end
30
+ }
31
+ all ? ary : ary.first
32
+ end
33
+
34
+ # a hash of all the names, keyed by the first letter
35
+ def self.read_hash(path: DATAFILE)
36
+ hsh = Hash.new { |h, k| h[k] = [] }
37
+ File.open(path).each_line { |l|
38
+ hsh[l[0]] << l.chomp
39
+ }
40
+ hsh
41
+ end
42
+
43
+ # convert 0-25 to a lowercase alpha
44
+ def self.key(val)
45
+ if val.is_a?(String)
46
+ if val.match(/^[0-9]/)
47
+ val = val[0..1].to_i
48
+ elsif val.match(/^[a-z]/i)
49
+ return val.downcase[0]
50
+ else
51
+ raise(ArgumentError, "can't handle #{val}")
52
+ end
53
+ end
54
+ CompSci::Names::ENGLISH_LOWER[val.to_i]
55
+ end
56
+
57
+ # return a pokemon sampled from those keyed by val
58
+ def self.sample(val)
59
+ ary = self.hash[self.key(val)]
60
+ ary.sample if ary
61
+ end
62
+ end
@@ -1,67 +1,141 @@
1
1
  module CompSci
2
2
  # has a value and an array of children; allows child gaps
3
3
  class Node
4
- attr_accessor :value
4
+ def self.display_line(nodes: [], width: 80)
5
+ block_width = [width / nodes.size, 1].max
6
+ nodes.map { |node|
7
+ str = node ? node.to_s : '_'
8
+ space = [(block_width + str.size) / 2, str.size + 1].max
9
+ str.ljust(space, ' ').rjust(block_width, ' ')
10
+ }.join
11
+ end
12
+
13
+ attr_accessor :value, :metadata
5
14
  attr_reader :children
6
15
 
7
- def initialize(value, children: [])
16
+ def initialize(value, children: 2, metadata: {})
8
17
  @value = value
9
- if children.is_a?(Integer)
10
- @children = Array.new(children)
11
- else
12
- @children = children
13
- end
14
- # @metadata = {}
18
+ @children = Array.new(children)
19
+ @metadata = metadata
15
20
  end
16
21
 
17
- def to_s
18
- @value.to_s
22
+ def [](idx)
23
+ @children[idx]
19
24
  end
20
25
 
21
- # This could be done directly with self.children, but #set_child is part
22
- # of the Node API.
23
- def set_child(idx, node)
26
+ def []=(idx, node)
24
27
  @children[idx] = node
25
28
  end
26
29
 
30
+ def to_s
31
+ @value.to_s
32
+ end
33
+
27
34
  def inspect
28
35
  "#<%s:0x%0xi @value=%s @children=[%s]>" %
29
- [self.class,
30
- self.object_id,
31
- self.to_s,
32
- @children.map(&:to_s).join(', ')]
36
+ [self.class, self.object_id, self, @children.join(', ')]
37
+ end
38
+
39
+ def display(width: 80)
40
+ lines = [self.class.display_line(nodes: [self], width: width)]
41
+ nodes = @children
42
+ while nodes.any? { |n| !n.nil? }
43
+ lines << self.class.display_line(nodes: nodes, width: width)
44
+ if nodes.size > 3**7
45
+ lines << "nodes.size = #{nodes.size}; abort render"
46
+ break
47
+ end
48
+ nodes = nodes.reduce([]) { |memo, n|
49
+ memo + Array.new(@children.size) { |i| n and n.children[i] }
50
+ }
51
+ end
52
+ lines.join("\n")
33
53
  end
34
54
  end
35
55
 
56
+ # TODO: implement key with @metadata !?!?!?!?
57
+
36
58
  # adds a key to Node; often the key is used to place the node in the
37
59
  # tree, independent of the value; e.g. key=priority, value=process_id
38
60
  class KeyNode < Node
39
- attr_accessor :key
61
+ class DuplicateKey < RuntimeError; end
62
+ class SearchError < RuntimeError; end
63
+
64
+ attr_reader :key, :duplicates
65
+
66
+ def self.key_cmp_idx(new_key, key, child_slots: 2, duplicates: false)
67
+ if child_slots < 2
68
+ raise(ArgumentError, "child_slots: #{child_slots} too small")
69
+ elsif child_slots == 2
70
+ raise(DuplicateKey, "#{new_key}") if new_key == key and !duplicates
71
+ new_key < key ? 0 : 1
72
+ elsif child_slots == 3
73
+ (new_key <=> key) + 1
74
+ else
75
+ raise(ArgumentError: "child_slots: #{child_slots} too big")
76
+ end
77
+ end
40
78
 
41
- def initialize(val, key: nil, children: [])
42
- @key = key
79
+ def initialize(val, key: nil, children: 2, duplicates: false)
43
80
  super(val, children: children)
81
+ @key, @duplicates = key, duplicates
44
82
  end
45
83
 
46
84
  def to_s
47
- [key, value].join(':')
85
+ [@key, @value].join(':')
86
+ end
87
+
88
+ # which child idx should handle key?
89
+ def cidx(key)
90
+ self.class.key_cmp_idx(key, @key,
91
+ child_slots: @children.size,
92
+ duplicates: @duplicates)
48
93
  end
49
- end
50
94
 
51
- # accumulate children; no child gaps
52
- class FlexNode < Node
53
- # These methods look like convenience methods, but they provide the
54
- # FlexNode interface also used by ChildFlexNode
55
- def add_child(node)
56
- @children << node
95
+ # works for 2 or 3 children
96
+ def insert(key, val)
97
+ idx = self.cidx(key)
98
+ if @children[idx]
99
+ @children[idx].insert(key, val)
100
+ else
101
+ @children[idx] = self.class.new(val, key: key,
102
+ children: @children.size,
103
+ duplicates: @duplicates)
104
+ end
57
105
  end
58
106
 
59
- def new_child(value)
60
- self.add_child self.class.new(value)
107
+ # returns a single node for binary search
108
+ # returns multiple nodes for ternary search
109
+ def search(key)
110
+ if @children.size == 2
111
+ self.search2(key)
112
+ elsif @children.size == 3
113
+ self.search3(key)
114
+ else
115
+ raise(SearchError, "can't search for @children.size children")
116
+ end
61
117
  end
62
118
 
63
- def add_parent(node)
64
- node.add_child(self)
119
+ # returns a single node or nil
120
+ def search2(key)
121
+ return self if key == @key
122
+ child = @children[self.cidx(key)]
123
+ child.search(key) if child
124
+ end
125
+
126
+ # returns an array of nodes, possibly empty
127
+ def search3(key)
128
+ if key == @key
129
+ nodes = [self]
130
+ node = @children[1]
131
+ while node
132
+ nodes << node
133
+ node = node.children[1]
134
+ end
135
+ return nodes
136
+ end
137
+ child = @children[self.cidx(key)]
138
+ child ? child.search(key) : []
65
139
  end
66
140
  end
67
141
 
@@ -69,7 +143,7 @@ module CompSci
69
143
  class ChildNode < Node
70
144
  attr_accessor :parent
71
145
 
72
- def initialize(value, children: [])
146
+ def initialize(value, children: 2)
73
147
  @parent = nil
74
148
  super(value, children: children)
75
149
  end
@@ -83,35 +157,11 @@ module CompSci
83
157
  @parent ? @parent.children : []
84
158
  end
85
159
 
86
- def set_child(idx, node)
160
+ # update both sides of the relationship
161
+ def []=(idx, node)
87
162
  node.parent ||= self
88
163
  raise "node has a parent: #{node.parent}" if node.parent != self
89
164
  @children[idx] = node
90
165
  end
91
-
92
- def set_parent(idx, node)
93
- @parent = node
94
- @parent.set_child(idx, self)
95
- end
96
- end
97
-
98
- # ChildNode which accumulates children with no gaps
99
- # It meets the FlexNode API but does not inherit from FlexNode since it
100
- # needs to reimplement each method; instead get parent stuff from ChildNode
101
- class ChildFlexNode < ChildNode
102
- def add_child(node)
103
- node.parent ||= self
104
- raise "node has a parent: #{node.parent}" if node.parent != self
105
- @children << node
106
- end
107
-
108
- def new_child(value)
109
- self.add_child self.class.new(value)
110
- end
111
-
112
- def add_parent(node)
113
- @parent = node
114
- @parent.add_child(self)
115
- end
116
166
  end
117
167
  end