stamina 0.3.0

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.
Files changed (80) hide show
  1. data/.gemtest +0 -0
  2. data/CHANGELOG.md +22 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +33 -0
  5. data/LICENCE.md +22 -0
  6. data/Manifest.txt +16 -0
  7. data/README.md +78 -0
  8. data/Rakefile +23 -0
  9. data/bin/adl2dot +12 -0
  10. data/bin/classify +12 -0
  11. data/bin/redblue +12 -0
  12. data/bin/rpni +12 -0
  13. data/example/adl/automaton.adl +49 -0
  14. data/example/adl/sample.adl +53 -0
  15. data/example/basic/characteristic_sample.adl +32 -0
  16. data/example/basic/target.adl +9 -0
  17. data/example/competition/31_test.adl +1500 -0
  18. data/example/competition/31_training.adl +1759 -0
  19. data/lib/stamina.rb +19 -0
  20. data/lib/stamina/adl.rb +298 -0
  21. data/lib/stamina/automaton.rb +1237 -0
  22. data/lib/stamina/automaton/walking.rb +336 -0
  23. data/lib/stamina/classifier.rb +37 -0
  24. data/lib/stamina/command/adl2dot_command.rb +73 -0
  25. data/lib/stamina/command/classify_command.rb +57 -0
  26. data/lib/stamina/command/redblue_command.rb +58 -0
  27. data/lib/stamina/command/rpni_command.rb +58 -0
  28. data/lib/stamina/command/stamina_command.rb +79 -0
  29. data/lib/stamina/errors.rb +20 -0
  30. data/lib/stamina/induction/commons.rb +170 -0
  31. data/lib/stamina/induction/redblue.rb +264 -0
  32. data/lib/stamina/induction/rpni.rb +188 -0
  33. data/lib/stamina/induction/union_find.rb +377 -0
  34. data/lib/stamina/input_string.rb +123 -0
  35. data/lib/stamina/loader.rb +0 -0
  36. data/lib/stamina/markable.rb +42 -0
  37. data/lib/stamina/sample.rb +190 -0
  38. data/lib/stamina/version.rb +14 -0
  39. data/stamina.gemspec +190 -0
  40. data/stamina.noespec +35 -0
  41. data/tasks/debug_mail.rake +78 -0
  42. data/tasks/debug_mail.txt +13 -0
  43. data/tasks/gem.rake +68 -0
  44. data/tasks/spec_test.rake +79 -0
  45. data/tasks/unit_test.rake +77 -0
  46. data/tasks/yard.rake +51 -0
  47. data/test/stamina/adl_test.rb +491 -0
  48. data/test/stamina/automaton_additional_test.rb +190 -0
  49. data/test/stamina/automaton_classifier_test.rb +155 -0
  50. data/test/stamina/automaton_test.rb +1092 -0
  51. data/test/stamina/automaton_to_dot_test.rb +64 -0
  52. data/test/stamina/automaton_walking_test.rb +206 -0
  53. data/test/stamina/exit.rb +3 -0
  54. data/test/stamina/induction/induction_test.rb +70 -0
  55. data/test/stamina/induction/redblue_mergesamestatebug_expected.adl +19 -0
  56. data/test/stamina/induction/redblue_mergesamestatebug_pta.dot +64 -0
  57. data/test/stamina/induction/redblue_mergesamestatebug_sample.adl +9 -0
  58. data/test/stamina/induction/redblue_test.rb +83 -0
  59. data/test/stamina/induction/redblue_universal_expected.adl +4 -0
  60. data/test/stamina/induction/redblue_universal_sample.adl +5 -0
  61. data/test/stamina/induction/rpni_inria_expected.adl +7 -0
  62. data/test/stamina/induction/rpni_inria_sample.adl +9 -0
  63. data/test/stamina/induction/rpni_test.rb +129 -0
  64. data/test/stamina/induction/rpni_test_pta.dot +22 -0
  65. data/test/stamina/induction/rpni_universal_expected.adl +4 -0
  66. data/test/stamina/induction/rpni_universal_sample.adl +4 -0
  67. data/test/stamina/induction/union_find_test.rb +124 -0
  68. data/test/stamina/input_string_test.rb +323 -0
  69. data/test/stamina/markable_test.rb +70 -0
  70. data/test/stamina/randdfa.adl +66 -0
  71. data/test/stamina/sample.adl +4 -0
  72. data/test/stamina/sample_classify_test.rb +149 -0
  73. data/test/stamina/sample_test.rb +218 -0
  74. data/test/stamina/small_dfa.dot +16 -0
  75. data/test/stamina/small_dfa.gif +0 -0
  76. data/test/stamina/small_nfa.dot +18 -0
  77. data/test/stamina/small_nfa.gif +0 -0
  78. data/test/stamina/stamina_test.rb +69 -0
  79. data/test/test_all.rb +7 -0
  80. metadata +279 -0
@@ -0,0 +1,188 @@
1
+ module Stamina
2
+ module Induction
3
+
4
+ #
5
+ # Implementation of the standard Regular Positive and Negative Induction (RPNI)
6
+ # algorithm. From a given sample, containing positive and negative strings, RPNI
7
+ # computes the smallest deterministic automaton compatible with the sample.
8
+ #
9
+ # See J. Oncina and P. Garcia, Infering Regular Languages in Polynomial Update
10
+ # Time, In N. Perez de la Blanca, A. Sanfeliu and E. Vidal, editors, Pattern
11
+ # Recognition and Image Analysis, volume 1 of Series in Machines Perception and
12
+ # Artificial Intelligence, pages 49-61, World Scientific, 1992.
13
+ #
14
+ # Example:
15
+ # # sample typically comes from an ADL file
16
+ # sample = Stamina::ADL.parse_sample_file('sample.adl')
17
+ #
18
+ # # let RPNI build the smallest dfa
19
+ # dfa = Stamina::Induction::RPNI.execute(sample, {:verbose => true})
20
+ #
21
+ # Remarks:
22
+ # - Constructor and instance methods of this class are public but not intended
23
+ # to be used directly. They are left public for testing purposes only.
24
+ # - This class intensively uses the Stamina::Induction::UnionFind class and
25
+ # methods defined in the Stamina::Induction::Commons module which are worth
26
+ # reading to understand the algorithm implementation.
27
+ #
28
+ class RPNI
29
+ include Stamina::Induction::Commons
30
+
31
+ # Union-find data structure used internally
32
+ attr_reader :ufds
33
+
34
+ # Additional options of the algorithm
35
+ attr_reader :options
36
+
37
+ # Creates an algorithm instance with given options.
38
+ def initialize(options={})
39
+ @options = options
40
+ end
41
+
42
+ #
43
+ # Merges a state of rank j with a state of lower rank i. This merge method
44
+ # includes merging for determinization.
45
+ #
46
+ # Preconditions:
47
+ # - States denoted by i and j are expected leader states (non merged ones)
48
+ # - States denoted by i and j are expected to be different
49
+ #
50
+ # Postconditions:
51
+ # - Union find is refined, states i and j having been merged, as well as all
52
+ # state pairs that need to be merged to ensure the deterministic property
53
+ # of the quotient automaton.
54
+ # - If the resulting quotient automaton is consistent with the negative sample,
55
+ # this method returns true and the refined union-find correctly encodes the
56
+ # quotient automaton. Otherwise, the method returns false and the union-find
57
+ # information must be considered inaccurate.
58
+ #
59
+ def merge_and_determinize(i, j)
60
+ # Make the union (keep additional merges to be performed in determinization)
61
+ # and recompute the user data attached to the new state group (new_data)
62
+ determinization = []
63
+ @ufds.union(i, j) do |d1, d2|
64
+ new_data = merge_user_data(d1, d2, determinization)
65
+ return false unless new_data
66
+ new_data
67
+ end
68
+
69
+ # Merge for determinization
70
+ determinization.each do |pair|
71
+ # we take the leader states of the pair to merge
72
+ pair = pair.collect{|i| @ufds.find(i)}
73
+ # do nothing if already the same leader state
74
+ next if pair[0]==pair[1]
75
+ # otherwise recurse or fail
76
+ return false unless merge_and_determinize(pair[0], pair[1])
77
+ end
78
+
79
+ # Everything seems ok!
80
+ true
81
+ end
82
+
83
+ #
84
+ # Makes a complete merge (including determinization), or simply do nothing if
85
+ # it leads accepting a negative string.
86
+ #
87
+ # Preconditions:
88
+ # - States denoted by i and j are expected leader states (non merged ones)
89
+ # - States denoted by i and j are expected to be different
90
+ #
91
+ # Postconditions:
92
+ # - Union find is refined, states i and j having been merged, as well as all
93
+ # state pairs that need to be merged to ensure the deterministic property
94
+ # of the quotient automaton.
95
+ # - If the resulting quotient automaton is consistent with the negative sample,
96
+ # this method returns true and the refined union-find correctly encodes the
97
+ # quotient automaton. Otherwise, the union find has not been changed.
98
+ #
99
+ def successfull_merge_or_nothing(i,j)
100
+ # try a merge and determinize inside a transaction on the ufds
101
+ @ufds.transactional do
102
+ merge_and_determinize(i, j)
103
+ end
104
+ end
105
+
106
+ #
107
+ # Main method of the algorithm. Refines the union find passed as first argument
108
+ # by merging well chosen state pairs. Returns the refined union find.
109
+ #
110
+ # Preconditions:
111
+ # - The union find _ufds_ is correctly initialized (contains :initial, :accepting,
112
+ # and :error boolean flags as well as a :delta sub hash)
113
+ #
114
+ # Postconditions:
115
+ # - The union find has been refined. It encodes a quotient automaton (of the PTA
116
+ # it comes from) such that all positive and negative strings of the underlying
117
+ # sample are correctly classified by it.
118
+ #
119
+ def main(ufds)
120
+ @ufds = ufds
121
+ puts "Starting RPNI (#{@ufds.size} states)" if @options[:verbose]
122
+ # First loop, iterating all PTA states
123
+ (1...@ufds.size).each do |i|
124
+ # we ignore those that have been previously merged
125
+ next if @ufds.slave?(i)
126
+ # second loop: states of lower rank, with ignore
127
+ (0...i).each do |j|
128
+ next if @ufds.slave?(j)
129
+ # try to merge this pair, including determinization
130
+ # simply break the loop if it works!
131
+ success = successfull_merge_or_nothing(i,j)
132
+ if success
133
+ puts "#{i} and #{j} successfully merged" if @options[:verbose]
134
+ break
135
+ end
136
+ end # j loop
137
+ end # i loop
138
+ @ufds
139
+ end
140
+
141
+ #
142
+ # Build the smallest DFA compatible with the sample given as input.
143
+ #
144
+ # Preconditions:
145
+ # - The sample is consistent (does not contains the same string both labeled as
146
+ # positive and negative) and contains at least one string.
147
+ #
148
+ # Postconditions:
149
+ # - The returned DFA is the smallest DFA that correctly labels the learning sample
150
+ # given as input.
151
+ #
152
+ # Remarks:
153
+ # - This instance version of RPNI.execute is not intended to be used directly and
154
+ # is mainly provided for testing purposes. Please use the class variant of this
155
+ # method if possible.
156
+ #
157
+ def execute(sample)
158
+ # create union-find
159
+ puts "Creating PTA and UnionFind structure" if @options[:verbose]
160
+ ufds = sample2ufds(sample)
161
+ # refine it
162
+ ufds = main(ufds)
163
+ # compute and return quotient automaton
164
+ ufds2dfa(ufds)
165
+ end
166
+
167
+ #
168
+ # Build the smallest DFA compatible with the sample given as input.
169
+ #
170
+ # Options (the _options_ hash):
171
+ # - :verbose can be set to true to trace algorithm execution on standard output.
172
+ #
173
+ # Preconditions:
174
+ # - The sample is consistent (does not contains the same string both labeled as
175
+ # positive and negative) and contains at least one string.
176
+ #
177
+ # Postconditions:
178
+ # - The returned DFA is the smallest DFA that correctly labels the learning sample
179
+ # given as input.
180
+ #
181
+ def self.execute(sample, options={})
182
+ RPNI.new(options).execute(sample)
183
+ end
184
+
185
+ end # class RPNI
186
+
187
+ end # module Induction
188
+ end # module Stamina
@@ -0,0 +1,377 @@
1
+ module Stamina
2
+ module Induction
3
+
4
+ #
5
+ # Implements an UnionFind data structure dedicated to state merging induction algorithms.
6
+ # For this purpose, this union-find handles mergeable user data as well as transactional
7
+ # support. See Stamina::Induction::Commons about the usage of this class (and mergeable
8
+ # user data in particular) by induction algorithms.
9
+ #
10
+ # == Example (probably easier than a long explanation)
11
+ #
12
+ # # create a union-find for 10 elements
13
+ # ufds = Stamina::Induction::UnionFind.new(10) do |index|
14
+ # # each element will be associated with a hash with data of interest:
15
+ # # smallest element, greatest element and concatenation of names
16
+ # {:smallest => index, :greatest => index, :names => index.to_s}
17
+ # end
18
+ #
19
+ # # each element is its own leader
20
+ # puts (0...10).all?{|s| ufds.leader?(s)} -> true
21
+ #
22
+ # # and their respective group number are the element indices themselve
23
+ # puts ufds.to_a -> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
24
+ #
25
+ # # now, let merge 4 with 0
26
+ # ufds.union(0, 4) do |d0, d4|
27
+ # {:smallest => d0[:smallest] < d4[:smallest] ? d0[:smallest] : d4[:smallest],
28
+ # :greatest => d0[:smallest] > d4[:smallest] ? d0[:smallest] : d4[:smallest],
29
+ # :names => d0[:names] + " " + d4[:names]}
30
+ # end
31
+ #
32
+ # # let see what happens on group numbers
33
+ # puts ufds.to_a -> [0, 1, 2, 3, 0, 5, 6, 7, 8, 9]
34
+ #
35
+ # # let now have a look on mergeable_data of the group of 0 (same result for 4)
36
+ # puts ufds.mergeable_data(0).inspect -> {:smallest => 0, :greatest => 4, :names => "0 4"}
37
+ #
38
+ # == Basic Union Find API
39
+ #
40
+ # A UnionFind data structure typically allows encoding a partition of elements (a
41
+ # partition is a collection of disjoint sets - aka a collection of groups). Basically,
42
+ # this class represents elements by successive indices (from 0 to size, the later being
43
+ # excluded). The partitioning information is kept in a array, associating a group number
44
+ # to each element. This group number is simply the index of the least element in the
45
+ # group (which means that group numbers are not necessarily consecutive). For example,
46
+ # the following arrays maps to the associated partitions:
47
+ #
48
+ # [0, 1, 2, 3, 4, 5] -> {{0}, {1}, {2}, {3}, {4}}
49
+ # [0, 0, 0, 0, 0, 0] -> {{0, 1, 2, 3, 4, 5}}
50
+ # [0, 1, 1, 0, 4, 4] -> {{0, 3}, {1, 2}, {5, 5}}
51
+ #
52
+ # The API of this basic union-find data structure is composed of the following
53
+ # methods:
54
+ # - new(size) (class method): builds an initial partition information over _size_
55
+ # elements. This initial partition keeps each element in its own group.
56
+ # - find(i): returns the group number of the i-th element
57
+ # - union(i, j): merge the group of the i-th element with the group of the j-th
58
+ # element. Note that i and j are elements, NOT group numbers.
59
+ #
60
+ # As we use least elements as group numbers, it is also interesting to know if a
61
+ # given element is that least element (aka leader element of the group) or not:
62
+ #
63
+ # - leader?(i): returns true if i is the group number of the i-th element, false
64
+ # otherwise. In other words, returns true if find(i)==i
65
+ # - slave?(i): the negation of leader?(i).
66
+ #
67
+ # == Handling User Data
68
+ #
69
+ # Even if this class represents elements by indices, it also allows keeping user
70
+ # data attached to each group. For this:
71
+ #
72
+ # - an initial user data is attached to each element at construction time by
73
+ # yielding a block (passing the element index as first argument and expecting
74
+ # user data as block return value).
75
+ # - the union(i, j) method allows a block to be given. It passes user data of i's
76
+ # and j's groups as arguments and expects the block to compute and return the
77
+ # merged user data for the new group.
78
+ # - mergeable_data(i) returns the current user data associated to the group of
79
+ # the i-th element.
80
+ # - mergeable_datas returns an array with user data attached to each group.
81
+ #
82
+ # Please note that user data are considered immutable values, and should never be
83
+ # changed... Only new ones can be created at union time. To ensures this good usage,
84
+ # user data are freezed by this class at creation time and union time.
85
+ #
86
+ # == Transactional support
87
+ #
88
+ # The main aim of this UnionFind is to make the implementation induction algorithms
89
+ # Stamina::Induction::RPNI and Stamina::Induction::RedBlue (sufficiently) efficient,
90
+ # simple and readable. These algorithms rely on a try-and-error strategy are must be
91
+ # able to revert the changes they have made during their last try. The transaction
92
+ # support implemented by this data structure helps them achieving this goal. For this
93
+ # we provide the following methods:
94
+ #
95
+ # - save_point: ensures that the internal state of the UnionFind can be restored if
96
+ # rollback is invoked later.
97
+ # - commit: informs the UnionFind that changes that have been made since the last
98
+ # invocation of save_point will not be reconsidered.
99
+ # - rollback: restores the internal state of the UnionFind that has been saved when
100
+ # save_point has been called.
101
+ #
102
+ # Please note that this class does not support sub-transactions.
103
+ #
104
+ class UnionFind
105
+
106
+ #
107
+ # An element of the union find, keeping the index of its leader element as well as
108
+ # mergeable user data. This class is not intended to be used by external users of the
109
+ # UnionFind data structure.
110
+ #
111
+ class Node
112
+
113
+ # Index of the parent element (on the way to the leader)
114
+ attr_accessor :parent
115
+
116
+ # Attached user data
117
+ attr_accessor :data
118
+
119
+ #
120
+ # Creates a default Node instance with a specific parent index and attached
121
+ # user data.
122
+ #
123
+ def initialize(parent, data)
124
+ @parent = parent
125
+ @data = data
126
+ end
127
+
128
+ #
129
+ # Duplicates this node, ensuring that future changes will not affect the copy.
130
+ # Please note that the user data itself is not duplicated and is not expected
131
+ # to change. This property (not changing user data) is respected by the RPNI
132
+ # and RedBlue classes as implemented in this library.
133
+ #
134
+ def dup
135
+ Node.new(@parent, @data)
136
+ end
137
+
138
+ end # class Node
139
+
140
+ #
141
+ # Number of elements in this union find
142
+ #
143
+ attr_reader :size
144
+
145
+ #
146
+ # (protected) Accessor on elements array, provided for duplication
147
+ #
148
+ attr_writer :elements
149
+
150
+ #
151
+ # Creates a default union find of a given size. Each element is initially in its own
152
+ # group. User data attached to each group is obtained by yielding a block, passing
153
+ # element index as first argument.
154
+ #
155
+ # Precondition:
156
+ # - size is expected to be strictly positive
157
+ #
158
+ def initialize(size)
159
+ @size = size
160
+ @elements = (0...size).collect do |i|
161
+ Node.new(i, block_given? ? yield(i).freeze : nil)
162
+ end
163
+ @changed = nil
164
+ end
165
+
166
+ # Union Find API ###########################################################
167
+
168
+ #
169
+ # Finds the group number of the i-th element (the group number is the least
170
+ # element of the group, aka _leader_).
171
+ #
172
+ # Preconditions:
173
+ # - i is a valid element: 0 <= i < size
174
+ #
175
+ # Postconditions:
176
+ # - returned value _found_ is such that <code>find(found)==found</code>
177
+ # - the union find data structure is not modified (no compression implemented).
178
+ #
179
+ def find(i)
180
+ while @elements[i].parent != i
181
+ i = @elements[i].parent
182
+ end
183
+ i
184
+ end
185
+
186
+ #
187
+ # Merges groups of the i-th element and j-th element, yielding a block to compute
188
+ # the merging of user data attached to their respective groups before merging.
189
+ #
190
+ # Preconditions:
191
+ # - This method allows i and j not to be leaders, but any element.
192
+ # - i and j are expected to be valid elements (0 <= i <= size, same for j)
193
+ #
194
+ # Postconditions:
195
+ # - groups of i and j have been merged. All elements of the two subgroups have
196
+ # the group number defined as <code>min(find(i),find(j))</code> (before
197
+ # merging)
198
+ # - if a block is provided, the user data attached to the new group is computed by
199
+ # yielding the block, passing mergable_data(i) and mergable_data(j) as arguments.
200
+ # The block is ecpected to return the merged data that will be kept for the new
201
+ # group.
202
+ # - If a transaction is pending, all required information is saved to restore
203
+ # the union-find structure if the transaction is rollbacked later.
204
+ #
205
+ def union(i, j)
206
+ i, j = find(i), find(j)
207
+ reversed = false
208
+ i, j, reversed = j, i, true if j<i
209
+
210
+ # Save i and j if in transaction and not already saved
211
+ if @changed
212
+ @changed[i] = @elements[i].dup unless @changed.has_key?(i)
213
+ @changed[j] = @elements[j].dup unless @changed.has_key?(j)
214
+ end
215
+
216
+ # Make the changes now
217
+ @elements[j].parent = i
218
+ if block_given?
219
+ d1, d2 = @elements[i].data, @elements[j].data
220
+ d1, d2 = d2, d1 if reversed
221
+ @elements[i].data = yield(d1, d2).freeze
222
+ else
223
+ nil
224
+ end
225
+ end
226
+
227
+ #
228
+ # Checks if an element is the leader of its group.
229
+ #
230
+ # Preconditions:
231
+ # - i is a valid element: 0 <= i < size
232
+ #
233
+ # Postconditions:
234
+ # - true if find(i)==i, false otherwise.
235
+ #
236
+ def leader?(i)
237
+ @elements[i].parent==i
238
+ end
239
+
240
+ #
241
+ # Checks if an element is a slave in its group (negation of leader?).
242
+ #
243
+ # Preconditions:
244
+ # - i is a valid element: 0 <= i < size
245
+ #
246
+ # Postconditions:
247
+ # - false if find(i)==i, true otherwise.
248
+ #
249
+ def slave?(i)
250
+ @elements[i].parent != i
251
+ end
252
+
253
+ # UserData API #############################################################
254
+
255
+ #
256
+ # Returns the mergeable data of each group in an array. No order of the
257
+ # groups is ensured by this method.
258
+ #
259
+ def mergeable_datas
260
+ indices = (0...size).select {|i| leader?(i)}
261
+ indices.collect{|i| @elements[i].data}
262
+ end
263
+
264
+ #
265
+ # Returns the mergeable data attached to the group of the i-th element.
266
+ #
267
+ # Preconditions:
268
+ # - This method allows i not to be leader, but any element.
269
+ # - i is a valid element: 0 <= i < size
270
+ #
271
+ def mergeable_data(i)
272
+ @elements[find(i)].data
273
+ end
274
+
275
+ # Transactional API ########################################################
276
+
277
+ #
278
+ # Makes a save point now. Internally ensures that future changes will be
279
+ # tracked and that a later rollback will restore the union find to the
280
+ # internal state it had before this call. This method should not be called
281
+ # if a transaction is already pending.
282
+ #
283
+ def save_point
284
+ @changed = {}
285
+ end
286
+
287
+ #
288
+ # Terminates the pending transaction by commiting all changes that have been
289
+ # done since the last save_point call. This method should not be called if no
290
+ # transaction is pending.
291
+ #
292
+ def commit
293
+ @changed = nil
294
+ end
295
+
296
+ #
297
+ # Rollbacks all changes that have been done since the last save_point call.
298
+ # This method will certainly fail if no transaction is pending.
299
+ #
300
+ def rollback
301
+ @changed.each_pair do |index, node|
302
+ @elements[index] = node
303
+ end
304
+ @changed = nil
305
+ end
306
+
307
+ #
308
+ # Makes a save point, yields the block. If it returns false or nil, rollbacks
309
+ # the transaction otherwise commits it. This method is a nice shortcut for
310
+ # the following piece of code
311
+ #
312
+ # ufds.save_point
313
+ # if try_something
314
+ # ufds.commit
315
+ # else
316
+ # ufds.rollback
317
+ # end
318
+ #
319
+ # which can also be expressed as:
320
+ #
321
+ # ufds.transactional do
322
+ # try_something
323
+ # end
324
+ #
325
+ # This method returns the value returned by the block
326
+ #
327
+ def transactional
328
+ save_point
329
+ returned = yield
330
+ if returned.nil? or returned == false
331
+ rollback
332
+ else
333
+ commit
334
+ end
335
+ returned
336
+ end
337
+
338
+ # Common utilities #########################################################
339
+
340
+ #
341
+ # Duplicates this data-structure, ensuring that no change on self or on the
342
+ # copy is shared. Please note that user datas themselve are not duplicated as
343
+ # they are considered immutable values (and freezed at construction and union).
344
+ #
345
+ def dup
346
+ copy = UnionFind.new(size)
347
+ copy.elements = @elements.collect{|e| e.dup}
348
+ copy
349
+ end
350
+
351
+ #
352
+ # Returns the partitioning information as as array with the group number of
353
+ # each element.
354
+ #
355
+ def to_a
356
+ (0...size).collect{|i| find(i)}
357
+ end
358
+
359
+ #
360
+ # Returns a string representation of this union find information.
361
+ #
362
+ def to_s
363
+ @elements.to_s
364
+ end
365
+
366
+ #
367
+ # Returns a string representation of this union find information.
368
+ #
369
+ def inspect
370
+ @elements.to_s
371
+ end
372
+
373
+ protected :elements=
374
+ end # class UnionFind
375
+
376
+ end # module Induction
377
+ end # module Stamina