stamina 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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