compsci 0.3.0.1 → 0.3.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/examples/tree.rb CHANGED
@@ -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,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
data/lib/compsci/fit.rb CHANGED
@@ -153,5 +153,22 @@ module CompSci
153
153
 
154
154
  return Math.exp(a), b, self.error(xys) { |x| (Math.exp(a) * (x ** b)) }
155
155
  end
156
+
157
+ def self.predict(model, a, b, x)
158
+ case model
159
+ when :constant
160
+ a
161
+ when :logarithmic
162
+ a + b * Math.log(x)
163
+ when :linear
164
+ a + b * x
165
+ when :exponential
166
+ a * Math::E ** (b * x)
167
+ when :power
168
+ a * x ** b
169
+ else
170
+ raise("unknown model: #{model}")
171
+ end
172
+ end
156
173
  end
157
174
  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
data/lib/compsci/heap.rb CHANGED
@@ -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 { |line|
38
+ hsh[line.chr] << line.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
data/lib/compsci/node.rb CHANGED
@@ -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
@@ -82,18 +82,22 @@ class CompSci::Simplex
82
82
  end
83
83
  end
84
84
 
85
- def self.problem(maximize: nil, constraints: [], **kwargs)
85
+ def self.problem(**kwargs)
86
+ self.new(*self.get_params(**kwargs))
87
+ end
88
+
89
+ def self.get_params(maximize: nil, constraints: [], **kwargs)
86
90
  if maximize
87
- obj, maximize = maximize, true
91
+ expr, maximize = maximize, true
88
92
  elsif kwargs[:minimize]
89
- obj, maximize = kwargs[:minimize], false
93
+ expr, maximize = kwargs[:minimize], false
90
94
  else
91
95
  raise(ArgumentError, "one of maximize/minimize expected")
92
96
  end
93
- unless obj.is_a?(String)
97
+ unless expr.is_a?(String)
94
98
  raise(ArgumentError, "bad expr: #{expr} (#{expr.class})")
95
99
  end
96
- obj_cof = Parse.expression(obj)
100
+ obj_cof = Parse.expression(expr)
97
101
 
98
102
  c = [] # coefficients of objective expression
99
103
  a = [] # array (per constraint) of the inequality's lhs coefficients
@@ -116,7 +120,7 @@ class CompSci::Simplex
116
120
  a.push cofs
117
121
  b.push rhs
118
122
  }
119
- self.new(c, a, b)
123
+ [c, a, b]
120
124
  end
121
125
 
122
126
  def self.maximize(expression, *ineqs)
@@ -1,9 +1,9 @@
1
- require 'compsci'
2
-
3
1
  # note, this work is based on https://github.com/rickhull/simplex
4
2
  # which was forked in 2017 from https://github.com/danlucraft/simplex
5
3
  # which had its last commit in 2013
6
4
 
5
+ module CompSci; end
6
+
7
7
  class CompSci::Simplex
8
8
  DEFAULT_MAX_PIVOTS = 10_000
9
9
 
@@ -26,35 +26,36 @@ class CompSci::Simplex
26
26
  @max_pivots = DEFAULT_MAX_PIVOTS
27
27
 
28
28
  # Problem dimensions; these never change
29
- @num_non_slack_vars = num_vars
30
- @num_constraints = num_inequalities
31
- @num_vars = @num_non_slack_vars + @num_constraints
29
+ @num_free_vars = num_vars
30
+ @num_basic_vars = num_inequalities
31
+ @total_vars = @num_free_vars + @num_basic_vars
32
32
 
33
33
  # Set up initial matrix A and vectors b, c
34
- @c = c.map { |flt| -1 * flt } + Array.new(@num_constraints, 0)
34
+ # store all input values as Rational (via #rationalize)
35
+ @c = c.map { |flt| -1 * flt.rationalize } + Array.new(@num_basic_vars, 0)
35
36
  @a = a.map.with_index { |ary, i|
36
- if ary.size != @num_non_slack_vars
37
+ if ary.size != @num_free_vars
37
38
  raise ArgumentError, "a is inconsistent"
38
39
  end
39
- # set diagonal to 1 (identity matrix?)
40
- ary + Array.new(@num_constraints) { |ci| ci == i ? 1 : 0 }
40
+ # add identity matrix
41
+ ary.map { |flt| flt.rationalize } +
42
+ Array.new(@num_basic_vars) { |ci| ci == i ? 1 : 0 }
41
43
  }
42
- @b = b
44
+ @b = b.map { |flt| flt.rationalize }
43
45
 
44
- # set initial solution: all non-slack variables = 0
45
- @basic_vars = (@num_non_slack_vars...@num_vars).to_a
46
+ @basic_vars = (@num_free_vars...@total_vars).to_a
46
47
  self.update_solution
47
48
  end
48
49
 
49
50
  # does not modify vector / matrix
50
51
  def update_solution
51
- @x = Array.new(@num_vars, 0)
52
+ @x = Array.new(@total_vars, 0)
52
53
 
53
54
  @basic_vars.each { |basic_var|
54
55
  idx = nil
55
- @num_constraints.times { |i|
56
+ @num_basic_vars.times { |i|
56
57
  if @a[i][basic_var] == 1
57
- idx =i
58
+ idx = i
58
59
  break
59
60
  end
60
61
  }
@@ -78,7 +79,7 @@ class CompSci::Simplex
78
79
  end
79
80
 
80
81
  def current_solution
81
- @x[0...@num_non_slack_vars]
82
+ @x[0...@num_free_vars]
82
83
  end
83
84
 
84
85
  def can_improve?
@@ -121,7 +122,7 @@ class CompSci::Simplex
121
122
  }
122
123
 
123
124
  # update A and B
124
- @num_constraints.times { |i|
125
+ @num_basic_vars.times { |i|
125
126
  next if i == pivot_row
126
127
  r = @a[i][pivot_column]
127
128
  @a[i] = @a[i].map.with_index { |val, j| val - r * @a[pivot_row][j] }
@@ -134,7 +135,7 @@ class CompSci::Simplex
134
135
  def pivot_row(column_ix)
135
136
  min_ratio = nil
136
137
  idx = nil
137
- @num_constraints.times { |i|
138
+ @num_basic_vars.times { |i|
138
139
  a, b = @a[i][column_ix], @b[i]
139
140
  next if a == 0 or (b < 0) ^ (a < 0)
140
141
  ratio = Rational(b, a)
@@ -168,6 +169,4 @@ class CompSci::Simplex
168
169
  lines.insert(1, "-"*max_line_length)
169
170
  lines.join("\n")
170
171
  end
171
-
172
-
173
172
  end