omf_rete 0.5 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
 
2
- = Introduction
2
+ # Introduction
3
+
4
+ __Warning: This is embarrassingly out of date. Check tests for a more accurate
5
+ reflection of what's implemented__
3
6
 
4
7
  This library implements a tuple store with a query and subscribe mechanism.
5
8
  A subscribe is effectively a standing query which executes a block whenever
@@ -10,65 +13,69 @@ The store holds same sized tuples with each value being assigned a name and
10
13
  type at creation to support varous convenience functions to create and retrieve
11
14
  tuples.
12
15
 
13
- The following code snippet creates a simple RDF store and adds a few triplets
16
+ The following code snippet creates a simple RDF store (tuple_length: 3) and adds a triplet
14
17
  to it.
15
18
 
16
- store = OMF::Rete::Store.new(3)
17
- store.add('myFridge', 'contains', 'milk')
19
+ eng = OMF::Rete.create_engine(tuple_length: 3)
20
+ eng.add_fact('myFridge', 'contains', 'milk')
18
21
 
19
- A filter consists of an array of tuple +patterns+ and a +block+ to be called when the store
22
+ A rule consists of an array of tuple +patterns+ and a +block+ to be called when the store
20
23
  contains a set of tuples matching the +pattern+.
21
24
 
22
25
  The following filter only looks for a single, specific tuple. The supplied block is called
23
26
  immediately if the tuple already exists in the store, or when such a tuple would be added at a later
24
27
  stage.
25
28
 
26
- store.subscribe(:report_problem, [
27
- ['myFridge', 'status', 'broken']
28
- ]) do |m|
29
- puts "My fridge is broken"
30
- end
29
+ eng = OMF::Rete.create_engine(tuple_length: 3)
30
+ eng.add_rule(:report_problem, [
31
+ ['myFridge', 'status', 'broken']
32
+ ]) do |m|
33
+ puts "My fridge is broken"
34
+ end
35
+ eng.add_fact('myFridge', 'status', 'ok')
36
+ eng.add_fact('myFridge', 'status', 'broken')
31
37
 
32
38
  The following filter contains two +patterns+ and therefore both need to be matched at the same
33
39
  time in order for the block to fire. Note, that the order these tuples are added to the store
34
40
  or the interval between is irrelevant.
35
41
 
36
- store.subscribe(:save_milk, [
37
- ['myFridge', 'status', 'broken'],
38
- ['myFridge', 'contains', 'milk'],
39
- ]) do |m|
40
- puts "Save the milk from my fridge"
41
- end
42
-
42
+ eng.subscribe(:save_milk, [
43
+ [:fridge?, 'status', 'broken'],
44
+ [:fridge?, 'contains', 'milk'],
45
+ ]) do |m|
46
+ puts "Save the milk from #{m.fridge?}"
47
+ end
48
+ eng.add_fact('myFridge', 'status', 'broken')
43
49
 
44
- So far the filter pattern were fully specified. The <tt>:_</tt> symbol can be used as a wildcard identifier.
50
+ So far the filter pattern were fully specified. The <tt>nil</tt> value can be used as a wildcard identifier.
45
51
  The following code snippet reports anything which is broken.
46
52
 
47
- store.subscribe(:something_broken, [
48
- [:_, 'status', 'broken']
49
- ]) do |m|
50
- puts "Something is broken"
51
- end
53
+ eng.subscribe(:something_broken, [
54
+ [nil, 'status', 'broken']
55
+ ]) do |m|
56
+ puts "Something is broken"
57
+ end
58
+ eng.add_fact('myFridge', 'status', 'broken')
52
59
 
53
60
  _Not implemented yet_
54
61
  Similar to OMF::Rete::Store#addNamed we can describe a pattern with a hash. Any value not named is automatically
55
62
  wildcarded. Therefore, an alternative represenation of the previous filter is as follows:
56
63
 
57
- store.subscribe(:something_broken, [
58
- {:pred => 'status', :obj => 'broken'}
59
- ]) do |m|
60
- puts "Something is broken"
61
- end
64
+ store.subscribe(:something_broken, [
65
+ {:pred => 'status', :obj => 'broken'}
66
+ ]) do |m|
67
+ puts "Something is broken"
68
+ end
62
69
 
63
70
  The +match+ argument to the block holds the context of the match and specifically, the tuples involved
64
71
  in the match.
65
72
 
66
- store.subscribe(:something_broken, [
67
- [:_, 'status', 'broken']
68
- ]) do |match|
69
- what = match.tuples[0][:subject]
70
- puts "#{what} is broken"
71
- end
73
+ store.subscribe(:something_broken, [
74
+ [:_, 'status', 'broken']
75
+ ]) do |match|
76
+ what = match.tuples[0][:subject]
77
+ puts "#{what} is broken"
78
+ end
72
79
 
73
80
  <tt>match.tuples</tt> returns an area of tuples one for each pattern. The matched tuple for the first pattern is at index 0,
74
81
  the second one at index 1, and so on. Individual values of a tuple can be retrieved through the initially declared
@@ -78,18 +85,19 @@ Let us assume we are monitoring many fridges, so if we want to report broken one
78
85
  that the +subject+ in both patterns in our second example are identical. Or in more technical terms, we need to +bind+ or +join+
79
86
  values across patterns. A binding variable is identified by a symbol with a trailing <b>?</b>.
80
87
 
81
- store.subscribe(:save_milk, [
82
- [:fridge?, 'status', 'broken'],
83
- [:fridge?, 'contains', 'milk'],
84
- ]) do |match|
85
- fridge = match[:fridge]
86
- puts "Save the milk from #{fridge}"
87
- end
88
+ store.subscribe(:save_milk, [
89
+ [:fridge?, 'status', 'broken'],
90
+ [:fridge?, 'contains', 'milk'],
91
+ ]) do |match|
92
+ fridge = match[:fridge]
93
+ puts "Save the milk from #{fridge}"
94
+ end
88
95
 
89
96
  <tt>match[bindingName]</tt> (without the '?') returns the value bound to <tt>:fridge?</tt> for this match.
90
97
  Obviously <tt>match.tuples[0][:subject]</tt> will return the same value.
91
98
 
92
- == Functions
99
+ ## Functions
100
+
93
101
 
94
102
  Pattern matches alone are not always sufficient. For instance, let us assume that we have also stored the age in years
95
103
  of each monitored fridge and want to replace each broken one which is older than 10 years. To describe such a filter
@@ -99,38 +107,38 @@ Functions are identified by the <tt>:PROC</tt> symbol in the first position of a
99
107
  name, and the list of parameters. Effectively, a function filters the values previosuly bound to a variable to those
100
108
  for which the function returns true.
101
109
 
102
- store.subscribe(:replace_old_ones, [
103
- [:fridge?, 'status', 'broken'],
104
- [:fridge?, 'age', :age?],
105
- [:PROC, :greater, :age?, 10]
106
- ]) do |match|
107
- puts "Replace #{match[:fridge]}"
108
- end
110
+ store.subscribe(:replace_old_ones, [
111
+ [:fridge?, 'status', 'broken'],
112
+ [:fridge?, 'age', :age?],
113
+ [:PROC, :greater, :age?, 10]
114
+ ]) do |match|
115
+ puts "Replace #{match[:fridge]}"
116
+ end
109
117
 
110
118
  <b>Design Note:</b> A more generic solution based on a 'lambda' is most likely cleaner. This is effectively
111
119
  identical to the final block, except that the block should return +true+ for tuples passing the filter,
112
120
  and +false+ for all others. To further simplify this and also reduce the search space, we can define a
113
121
  +filter+ function which takes a list of bound variables and calls the associated block with specific bindings.
114
122
 
115
- store.subscribe(:replace_old_ones, [
116
- [:fridge?, 'status', 'broken'],
117
- [:fridge?, 'age', :age?],
118
- filter(:age?) { |age| age > 10 }
119
- ]) do |match|
120
- puts "Replace #{match[:fridge]}"
121
- end
123
+ store.subscribe(:replace_old_ones, [
124
+ [:fridge?, 'status', 'broken'],
125
+ [:fridge?, 'age', :age?],
126
+ filter(:age?) { |age| age > 10 }
127
+ ]) do |match|
128
+ puts "Replace #{match[:fridge]}"
129
+ end
122
130
 
123
- == Set Operators
131
+ ### Set Operators
124
132
 
125
133
  Let us assume we want the store to not only reflect the current facts but the entire history of a system. We
126
134
  can achieve that by adding a timestamp to each fact and never retract facts.
127
135
 
128
- store = OMF::Rete::Store.new(:subj => String, :pred => String, :obj => Object, :tstamp => Time)
136
+ store = OMF::Rete::Store.new(:subj => String, :pred => String, :obj => Object, :tstamp => Time)
129
137
 
130
138
  This now allows us to capture that a fridge broke on a specific date and was fixed some times later.
131
139
 
132
- store.add('myFridge', 'status', 'broken', '2008-12-20')
133
- store.add('myFridge', 'status', 'ok', '2008-12-22')
140
+ store.add('myFridge', 'status', 'broken', '2008-12-20')
141
+ store.add('myFridge', 'status', 'ok', '2008-12-22')
134
142
 
135
143
  However, how can we now determine that a specific fridge is CURRENTLY broken? The pattern
136
144
  <tt>[:f?, 'status' 'broken']</tt> will identify all fridges which are currently broken, as well as those
@@ -141,21 +149,21 @@ the filter picks the one with the most recent timestamp.
141
149
  The current syntax achieves this through special match values. For instance, <tt>:LATEST</tt> for <tt>Time</tt>
142
150
  types picks the most recent fact.
143
151
 
144
- [:fridge?, 'status', :_, :LATEST]
152
+ [:fridge?, 'status', :_, :LATEST]
145
153
 
146
154
  To find all currently broken fridges we need to bind this to all broken status facts.
147
155
 
148
- store.subscribe(:broken_lately, [
149
- [:fridge?, 'status', :_, :LATEST],
150
- [:fridge?, 'status', 'broken']
151
- ]) do |match|
152
- puts "#{match[:fridge]} is broken"
153
- end
156
+ store.subscribe(:broken_lately, [
157
+ [:fridge?, 'status', :_, :LATEST],
158
+ [:fridge?, 'status', 'broken']
159
+ ]) do |match|
160
+ puts "#{match[:fridge]} is broken"
161
+ end
154
162
 
155
163
  <b>Design Note:</b> This seems to be a fairly ad-hoc syntax. Is there a better one? This assumes that there is no join
156
164
  on any of the bound variables, they are simply keys for the sets. But overloading functionality always adds complexity.
157
165
 
158
- == Negated Conditions
166
+ ## Negated Conditions
159
167
 
160
168
  Now let us consider we know that our fridge is broken and we want to monitor any future status updates.
161
169
  There may be many different status types and we are interested in all of them as long as they are
@@ -163,12 +171,12 @@ different to 'broken'. In other words, we need a way to describe what is refered
163
171
  condition' and is defined by a leading <tt>:NOT</tt>, followed by one or multiple patterns describing
164
172
  what should NOT be in the store.
165
173
 
166
- store.subscribe(:find_latest, [
167
- ['My Fridge', :status, :_, :LATEST],
168
- [:NOT, ['My Fridge', 'status', 'broken']]
169
- ]) do |match|
170
- puts "Status for my fridge changed to '#{match.tuples[0][:obj]}."
171
- end
174
+ store.subscribe(:find_latest, [
175
+ ['My Fridge', :status, :_, :LATEST],
176
+ [:NOT, ['My Fridge', 'status', 'broken']]
177
+ ]) do |match|
178
+ puts "Status for my fridge changed to '#{match.tuples[0][:obj]}."
179
+ end
172
180
 
173
181
  Please note that the above example fails to report when my fridge is reported as broken again.
174
182
 
@@ -27,7 +27,11 @@ module OMF::Rete
27
27
  def addTuple(tuple)
28
28
  raise 'Abstract class'
29
29
  end
30
-
30
+
31
+ def removeTuple(tuple)
32
+ raise 'Abstract class'
33
+ end
34
+
31
35
  # Call block for every tuple stored in this set currently and
32
36
  # in the future. In other words, the block may be called even after this
33
37
  # method returns.
@@ -8,25 +8,25 @@ module OMF::Rete
8
8
  # removed.
9
9
  #
10
10
  # The IndexedTupleSet is defined by a +description+ and an
11
- # +indexPattern+.
11
+ # +indexPattern+.
12
12
  #
13
13
  # The +description+ is an array of the
14
14
  # same length as the tuples maintained. Each element,
15
15
  # if not nil, names the binding variable associated with it.
16
- # The position of a binding can be retrieved with
16
+ # The position of a binding can be retrieved with
17
17
  # +index_for_binding+.
18
18
  #
19
19
  # The +indexPattern+ describes which elements of the inserted
20
- # tuple are being combined in an array to form the index
21
- # key for each internal tuple. The elements in the +indexPattern+
20
+ # tuple are being combined in an array to form the index
21
+ # key for each internal tuple. The elements in the +indexPattern+
22
22
  # are described by the binding name.
23
23
  #
24
24
  #
25
25
  class IndexedTupleSet < AbstractTupleSet
26
-
26
+
27
27
  attr_reader :indexPattern
28
28
  attr_writer :transient # if true only process tuple but don't store it
29
-
29
+
30
30
  def initialize(description, indexPattern, source = nil, opts = {})
31
31
  super description, source
32
32
  if (indexPattern.length == 0)
@@ -39,7 +39,7 @@ module OMF::Rete
39
39
 
40
40
  @index = {}
41
41
  end
42
-
42
+
43
43
  def addTuple(tuple)
44
44
  key = @indexMap.collect do |ii|
45
45
  tuple[ii]
@@ -58,12 +58,38 @@ module OMF::Rete
58
58
  end
59
59
  tuple # return added tuple
60
60
  end
61
-
61
+
62
+ def removeTuple(tuple)
63
+ key = @indexMap.collect do |ii|
64
+ tuple[ii]
65
+ end
66
+
67
+ if @transient
68
+ @onRemoveBlockWithIndex.call(key, tuple) if @onRemoveBlockWithIndex
69
+ @onRemoveBlock.call(tuple) if @onRemoveBlock
70
+ else
71
+ vset = @index[key]
72
+ if vset
73
+ vset.delete(tuple)
74
+ @onRemoveBlockWithIndex.call(key, tuple) if @onRemoveBlockWithIndex
75
+ @onRemoveBlock.call(tuple) if @onRemoveBlock
76
+ end
77
+ end
78
+ tuple # return removed tuple
79
+ end
80
+
81
+ # Clear index
82
+ def clear()
83
+ @onRemoveBlockWithIndex.call(nil, nil) if @onRemoveBlockWithIndex
84
+ @onRemoveBlock.call(nil) if @onRemoveBlock
85
+ @index = {}
86
+ end
87
+
62
88
  # Call block for every tuple stored in this set currently and
63
89
  # in the future. In other words, the block may be called even after this
64
- # method returns.
90
+ # method returns.
65
91
  #
66
- # The block will be called with one parameters, the
92
+ # The block will be called with one parameters, the
67
93
  # tuple added.
68
94
  #
69
95
  # Note: Only one +block+ can be registered at a time
@@ -80,9 +106,9 @@ module OMF::Rete
80
106
 
81
107
  # Call block for every tuple stored in this set currently and
82
108
  # in the future. In other words, the block may be called even after this
83
- # method returns.
109
+ # method returns.
84
110
  #
85
- # The block will be called with two parameters, the index of the tuple followed by the
111
+ # The block will be called with two parameters, the index of the tuple followed by the
86
112
  # tuple itself.
87
113
  #
88
114
  # Note: Only one +block+ can be registered at a time
@@ -96,14 +122,44 @@ module OMF::Rete
96
122
  @onAddBlockWithIndex = block
97
123
  end
98
124
 
99
- # Return the set of tuples index by +key+.
125
+ # Call block for every tuple removed from this set in the future.
126
+ # In other words, the block may be called after this
127
+ # method returns.
128
+ #
129
+ # The block will be called with one parameters, the
130
+ # tuple removed. If the parameter is nil, everything has
131
+ # been removed (cleared)
132
+ #
133
+ # Note: Only one +block+ can be registered at a time
134
+ #
135
+ def on_remove(&block)
136
+ @onRemoveBlock = block
137
+ end
138
+
139
+
140
+ # Call block for every tuple removed from this set
141
+ # in the future. In other words, the block may be called even after this
142
+ # method returns.
143
+ #
144
+ # The block will be called with two parameters, the index of the tuple followed by the
145
+ # tuple itself. If both parameters are nil, everything has
146
+ # been removed (cleared)
147
+ #
148
+ # Note: Only one +block+ can be registered at a time
149
+ #
150
+ def on_remove_with_index(&block)
151
+ @onRemoveBlockWithIndex = block
152
+ end
153
+
154
+
155
+ # Return the set of tuples index by +key+.
100
156
  # Will return nil if nothing is stored for +key+
101
157
  #
102
158
  def [](key)
103
159
  res = @index[key]
104
160
  res
105
161
  end
106
-
162
+
107
163
  # Return all stored tuples in an array.
108
164
  def to_a
109
165
  a = []
@@ -114,16 +170,27 @@ module OMF::Rete
114
170
  end
115
171
  a
116
172
  end
117
-
173
+
174
+ # Return all stored tuples in a set.
175
+ def to_set
176
+ a = Set.new
177
+ @index.each_value do |s|
178
+ s.each do |t|
179
+ a << t
180
+ end
181
+ end
182
+ a
183
+ end
184
+
118
185
  def describe(out = STDOUT, offset = 0, incr = 2, sep = "\n")
119
186
  out.write(" " * offset)
120
187
  desc = @description.collect do |e| e || '*' end
121
188
  out.write("ts: [#{desc.join(', ')}]")
122
189
  ind = @indexMap.collect do |i| @description[i] end
123
190
  out.write(" (index: [#{ind.sort.join(', ')}])#{sep}")
124
- @source.describe(out, offset + incr, incr, sep) if @source
191
+ @source.describe(out, offset + incr, incr, sep) if @source
125
192
  end
126
-
193
+
127
194
  end # class IndexedTupleSet
128
195
  end # module
129
196
 
@@ -2,27 +2,27 @@ require 'omf_rete/indexed_tuple_set'
2
2
 
3
3
  module OMF::Rete
4
4
 
5
- # This class implements the join operation between two
5
+ # This class implements the join operation between two
6
6
  # +IndexedTupleSets+ feeding into a third, result tuple set.
7
7
  # The size of both incoming tuple sets needs to be identical and they
8
8
  # are supposed to be indexed on the same list of variables as this is
9
9
  # what they wil be joined at.
10
- #
11
- # Implementation Note: We first calculate a +combinePattern+
10
+ #
11
+ # Implementation Note: We first calculate a +combinePattern+
12
12
  # from the +description+ of the result set.
13
13
  # The +combinePattern+ describes how to create a joined tuple to insert
14
- # into the result tuple set. The +combinePattern+ is an array of
15
- # the same size as the result tuple. Each element is a 2-array
14
+ # into the result tuple set. The +combinePattern+ is an array of
15
+ # the same size as the result tuple. Each element is a 2-array
16
16
  # with the first element describing the input set (0 .. left, 1 .. right)
17
17
  # and the second one the index from which to take the value.
18
- #
18
+ #
19
19
  #
20
20
  class JoinOP
21
21
  def initialize(leftSet, rightSet, resultSet)
22
22
  @resultSet = resultSet
23
23
  @left = leftSet
24
24
  @right = rightSet
25
-
25
+
26
26
  @combinePattern = resultSet.description.collect do |bname|
27
27
  side = 0
28
28
  unless (i = leftSet.index_for_binding(bname))
@@ -35,7 +35,8 @@ module OMF::Rete
35
35
  [side, i]
36
36
  end
37
37
  @resultLength = @combinePattern.length
38
-
38
+ @results = {} # Keep track of how may input tuples create the same result - necessary for 'removeTuple'
39
+
39
40
  leftSet.on_add_with_index do |index, ltuple|
40
41
  if (rs = rightSet[index])
41
42
  rs.each do |rtuple|
@@ -50,7 +51,30 @@ module OMF::Rete
50
51
  end
51
52
  end
52
53
  end
53
-
54
+
55
+ leftSet.on_remove_with_index do |index, ltuple|
56
+ if (index.nil?)
57
+ clear_result()
58
+ else
59
+ if (rs = rightSet[index])
60
+ rs.each do |rtuple|
61
+ remove_result(ltuple, rtuple)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ rightSet.on_remove_with_index do |index, rtuple|
67
+ if (index.nil?)
68
+ clear_result()
69
+ else
70
+ if (ls = leftSet[index])
71
+ ls.each do |ltuple|
72
+ remove_result(ltuple, rtuple)
73
+ end
74
+ end
75
+ end
76
+ end
77
+
54
78
  # Supporting 'check_for_tuple'
55
79
  @left_pattern = @left.description.map do |bname|
56
80
  @resultSet.index_for_binding(bname)
@@ -60,7 +84,7 @@ module OMF::Rete
60
84
  end
61
85
 
62
86
  end
63
-
87
+
64
88
  # Check if +tuple+ can be produced by this join op. We first
65
89
  # check if we can find a match on one side and then request
66
90
  # from the other side all the tuples which would lead to full
@@ -76,19 +100,19 @@ module OMF::Rete
76
100
  end
77
101
  return false
78
102
  end
79
-
103
+
80
104
  def description()
81
105
  @resultSet.description
82
106
  end
83
-
107
+
84
108
  def describe(out = STDOUT, offset = 0, incr = 2, sep = "\n")
85
109
  out.write(" " * offset)
86
110
  result = @combinePattern.collect do |side, index|
87
111
  (side == 0) ? @left.binding_at(index) : @right.binding_at(index)
88
112
  end
89
113
  out.write("join: [#{@left.indexPattern.sort.join(', ')}] => [#{result.sort.join(', ')}]#{sep}")
90
- @left.describe(out, offset + incr, incr, sep)
91
- @right.describe(out, offset + incr, incr, sep)
114
+ @left.describe(out, offset + incr, incr, sep)
115
+ @right.describe(out, offset + incr, incr, sep)
92
116
  end
93
117
 
94
118
  private
@@ -104,10 +128,46 @@ module OMF::Rete
104
128
  result[i] = t[index]
105
129
  i += 1
106
130
  end
107
- @resultSet.addTuple(result)
131
+
132
+ if @results.key? result
133
+ @results[result] = @results[result] + 1
134
+ else
135
+ @results[result] = 1
136
+ @resultSet.addTuple(result)
137
+ end
138
+ #puts "add: #{result.inspect} (#{@results.inspect})"
108
139
  end
109
-
110
-
111
-
140
+
141
+ def remove_result(ltuple, rtuple)
142
+ unless @resultLength
143
+ i = 2
144
+ end
145
+ result = Array.new(@resultLength)
146
+ i = 0
147
+ @combinePattern.each do |setId, index|
148
+ t = setId == 0 ? ltuple : rtuple
149
+ result[i] = t[index]
150
+ i += 1
151
+ end
152
+ # Remove it from result set if it has only been added once
153
+ #puts "remove: #{result.inspect} (#{@results.inspect})"
154
+ if count = @results[result]
155
+ if count == 1
156
+ @results.delete(result)
157
+ @resultSet.removeTuple(result)
158
+ else
159
+ @results[result] = count - 1
160
+ end
161
+ else
162
+ raise "Should never happen"
163
+ end
164
+ end
165
+
166
+ def clear_result()
167
+ @results = {}
168
+ @resultSet.clear
169
+ end
170
+
171
+
112
172
  end # class
113
173
  end # module
@@ -1,15 +1,16 @@
1
1
 
2
2
  require 'omf_rete/tuple_stream'
3
+ require 'omf_rete/join_op'
3
4
 
4
5
  module OMF::Rete
5
6
  module Planner
6
-
7
-
7
+
8
+
8
9
  # This class represents a planned join op.
9
- #
10
+ #
10
11
  #
11
12
  class JoinPlan < AbstractPlan
12
-
13
+
13
14
  # stream1 - first stream to join
14
15
  # stream2 - second stream to join
15
16
  # joinSet - set of bindings to join on
@@ -17,8 +18,8 @@ module OMF::Rete
17
18
  # coverSet - set of leaf nodes contributing to this result
18
19
  #
19
20
  def initialize(stream1, stream2, joinSet, resultSet, coverSet, planBuilder)
20
- super coverSet, resultSet
21
-
21
+ super coverSet, resultSet
22
+
22
23
  @planBuilder = planBuilder
23
24
  @left = stream1
24
25
  @right = stream2
@@ -34,7 +35,7 @@ module OMF::Rete
34
35
  description = @result_set.to_a.sort
35
36
  resultSet = IndexedTupleSet.new(description, indexPattern, nil, opts)
36
37
  end
37
-
38
+
38
39
  indexPattern = @join_set.to_a
39
40
  leftSet = @left.materialize(indexPattern, nil, opts)
40
41
  rightSet = @right.materialize(indexPattern, nil, opts)
@@ -46,7 +47,7 @@ module OMF::Rete
46
47
  # Create a hash for this plan which allows us to
47
48
  # to identify identical plans.
48
49
  #
49
- # Please note, that there is most likely a mroe efficient way to
50
+ # Please note, that there is most likely a mroe efficient way to
50
51
  # calculate a hash with the above properties
51
52
  #
52
53
  def hash()
@@ -58,7 +59,7 @@ module OMF::Rete
58
59
  end
59
60
  @hash
60
61
  end
61
-
62
+
62
63
  # Return the cost of this plan.
63
64
  #
64
65
  # TODO: Some more meaningful heuristic will be nice
@@ -69,25 +70,25 @@ module OMF::Rete
69
70
  rcost = @right.cost()
70
71
  #@cost = 1 + 1.2 * (lcost > rcost ? lcost : rcost)
71
72
  @cost = 1 + 1.2 * (lcost + rcost)
72
-
73
+
73
74
  end
74
75
  @cost
75
76
  end
76
-
77
+
77
78
  def describe(out = STDOUT, offset = 0, incr = 2, sep = "\n")
78
79
  out.write(" " * offset)
79
80
  result = @result_set.to_a.sort
80
81
  join = @join_set.to_a.sort
81
82
  out.write("join: [#{join.join(', ')}] => [#{result.join(', ')}] cost: #{cost}#{sep}")
82
- @left.describe(out, offset + incr, incr, sep)
83
- @right.describe(out, offset + incr, incr, sep)
83
+ @left.describe(out, offset + incr, incr, sep)
84
+ @right.describe(out, offset + incr, incr, sep)
84
85
  end
85
-
86
+
86
87
  def to_s
87
88
  result = @result_set.to_a.sort
88
89
  join = @join_set.to_a.sort
89
90
  "JoinPlan [#{join.join(', ')}] out: [#{result.join(', ')}]"
90
- end
91
+ end
91
92
  end # PlanBuilder
92
93
 
93
94
  end # Planner