rlsm 0.2.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -4,32 +4,7 @@ README.txt
4
4
  Rakefile
5
5
  bin/smon
6
6
  lib/data/monoids.db
7
+ lib/dfa.rb
8
+ lib/monoid.rb
9
+ lib/re.rb
7
10
  lib/rlsm.rb
8
- lib/rlsm/dfa.rb
9
- lib/rlsm/exceptions.rb
10
- lib/rlsm/mgen.rb
11
- lib/rlsm/monkey_patching.rb
12
- lib/rlsm/monoid.rb
13
- lib/rlsm/monoid_db.rb
14
- lib/rlsm/regexp.rb
15
- lib/rlsm/regexp_nodes/concat.rb
16
- lib/rlsm/regexp_nodes/primexp.rb
17
- lib/rlsm/regexp_nodes/renodes.rb
18
- lib/rlsm/regexp_nodes/star.rb
19
- lib/rlsm/regexp_nodes/union.rb
20
- lib/smon/commands/db_find.rb
21
- lib/smon/commands/db_stat.rb
22
- lib/smon/commands/exit.rb
23
- lib/smon/commands/help.rb
24
- lib/smon/commands/intro.rb
25
- lib/smon/commands/monoid.rb
26
- lib/smon/commands/quit.rb
27
- lib/smon/commands/regexp.rb
28
- lib/smon/commands/reload.rb
29
- lib/smon/commands/show.rb
30
- lib/smon/presenter.rb
31
- lib/smon/presenter/txt_presenter.rb
32
- lib/smon/smon.rb
33
- test/dfa_spec.rb
34
- test/monoid_spec.rb
35
- test/regexp_spec.rb
data/README.txt CHANGED
File without changes
data/Rakefile CHANGED
File without changes
data/bin/smon CHANGED
@@ -1,4 +1,2 @@
1
1
  #! /usr/bin/env ruby
2
- require File.join(File.dirname(__FILE__), '..', 'lib', 'smon', 'smon')
3
-
4
- Smon.new
2
+ puts "Comming soon"
File without changes
@@ -0,0 +1,656 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'rlsm'))
2
+ require 'monoid'
3
+ require "re"
4
+
5
+ =begin rdoc
6
+ ==Basic theory of deterministic finite automaton (short DFA)
7
+ ===Terminology
8
+ We will call a finite nonempty set an _alphabet_ and we will call an element of an alphabet a _letter_.
9
+
10
+ The free monoid generated by an alphabet +A+ will be denoted by +A*+ and is also called the _Kleene_-_closure_ of +A+. An element of +A*+ will be called a _word_ and will be written in the following text in a string notation: 'abcaac...'.
11
+
12
+ ===Definition of a DFA
13
+ A DFA is a five tuple <tt>(A,S,i,F,T)</tt> where
14
+ * +A+ is an alphabet,
15
+ * +S+ is a nonempty set,
16
+ * +i+ is an element of +S+,
17
+ * +F+ is a subset of +S+ and
18
+ * <tt>T:AxS -> S</tt> is a function.
19
+
20
+ The elements of +S+ are called _states_, +i+ is the _initial_ _state_ and elements of +F+ are called _final_ _states_ (or _accepting_ _states_). The function +T+ is called the _transition_ _function_ of the DFA.
21
+
22
+
23
+ In the following we will define some properties of a DFA and of states which will be of some interest:
24
+
25
+ ===The generalized transition function +T*+
26
+ Given a DFA, a generalized version of the transition function <tt>T*: A*xS -> S</tt> can be recursivly defined by
27
+ T*('',s) := s
28
+ T*('a',s) := T(a,s)
29
+ T*('abcd...',s) := T*('bcd...',T(a,s))
30
+
31
+ It isn't really necessary to define +T*+, but it helps to define further properties in a concise way.
32
+
33
+ ===Isomorphic DFAs
34
+ Like always we want to describe in what cases we consider two DFAs to be the same:
35
+
36
+ Let <tt>M=(A,S,i,F,T), N=(A',S',i',F',T')</tt> be two DFAs. A bijective map <tt>I : S -> S'</tt> is called an _isomorphism_ iff
37
+ 1. <tt>A = A'</tt>
38
+ 2. <tt>I(i) = i'</tt>
39
+ 3. For all +f+ in +F+ it is +I(f)+ in +F'+
40
+ 4. For all states +s+ in +S+ and all letters +l+ in +A+ it is <tt>I(T(l,s)) = T'(l,I(s))</tt>
41
+
42
+ We say +M+ is _isomorph_ _to_ +N+ if such an isomorphism exists. We see, two isomorphic DFAs differs only in the "names" of the states, so a distinction isn't very useful.
43
+
44
+ ===The language of a DFA
45
+ Let +M+ be a DFA. The subset of +A*+ defined by
46
+ L(M) := { w in A* | T*(w,i) is a final state }
47
+ is a formal language, in fact it is a _regular_ _language_. It is called the _language_ _generated_ _by_ +M+.
48
+
49
+ In almost all cases, if we study a DFA +M+ we are only interested in the language +L(M)+. Obviously two isomorphic DFAs generates the same language, but there are also non-isomorphic DFAs which generates the same language. This motivates the following definition: two DFAs +M+ and +N+ are _equivalent_ iff <tt>L(M) = L(N)</tt>.
50
+
51
+ It is easy to see, that this relation is an equivalence relation and we can define the equivalence class of a DFA +M+:
52
+ [M] := { N | N is a DFA which is equivalent to M }
53
+
54
+ ===The minimal DFA of a regular language
55
+ Let +L+ be a regular language and +M+ a DFA with <tt>L(M) = L</tt>. We will define a order '<' on the set +[M]+ by
56
+ N < M :<=> N has less states than M
57
+ Because the number of states is an integer, there exists a DFA which has a minimal number of states. Such a DFA is called a _minimal_ DFA for +L+.
58
+
59
+ It has been shown that two minimal DFAs for a language +L+ are isomorphic. So we can talk about _the_ minimal DFA for +L+.
60
+
61
+ ==An algorithm to find a minimal DFA
62
+ Let <tt>M = (A,S,i,F,T)</tt> be a DFA. The goal of this paragraph is to find an equivalent minimal DFA for +L(M)+.
63
+
64
+ Because we know, that some states have no influence on the generated language, we must find some criterias which states aren't required.
65
+
66
+ ===Unreachable states
67
+ We notice, that in the definition it isn't required, that for each state +s+ a word +w+ exists, such that <tt>T*(w,i) = s</tt>. We will call a state +s+ _reachable_ if such a word exists and _unreachable_ otherwise.
68
+
69
+ It is easy to see, that an unreachable state has no impact on the generated language, so a first step towards a minimal DFA is to reject all unreachable states.
70
+
71
+ A problem may be, that after removing the unreachable states, say the remaining states are +S'+, it is now possible that <tt>T|AxS'</tt> isn't defined everywhere. We will treat this in the next paragrapgh
72
+
73
+ ===Completeness of a DFA
74
+ It is convenient to discard the requirement, that +T+ is defined on +AxS+. It is for most use-cases sufficent, that +T+ is a function on a subset +D(T)+ of +AxS+.
75
+
76
+ In this case, we say a state +s+ _accepts_ the letter +c+ if <tt>(c,s)</tt> is an element of +D(T)+. We say a DFA is _complete_ iff +D(T)=AxS+, i.e. each state accepts all alphabet letters.
77
+
78
+ ====An algorithm to complete a DFA
79
+ If we have a noncomplete DFA <tt>(A,S,I,F,T)</tt> we can complete it in the following way:
80
+ 1. Add a _trap_ _state_ +t+.
81
+ 2. Define <tt>T':Su{t} -> Su{t}</tt> by <tt>T'=T</tt> for points where +T+ is defined and <tt>T'(c,s) = t</tt> elsewhere.
82
+ Now the DFA <tt>(A,Su{t},I,F,T')</tt> is complete.
83
+
84
+ ====Remark
85
+ For a noncomplete DFA +M+ it is also possible to define a generalized transition function: If +N+ is the in the above way completed DFA of +M+ and +F+ its generalized transition function. We define
86
+ D(T*) := { (w,s) | w in A*, F(w,s) != t }
87
+ and
88
+ T* := F|D(T*)
89
+ Now +T*+ is a generalized transition function for a noncomplete DFA.
90
+
91
+ Also the completion doesn't change the generated language.
92
+
93
+ ===Equivalent states
94
+ We say two states <tt>s,t</tt> are _equivalent_ iff for all words +w+ <tt>T*(w,s)</tt> is a final state iff and only if <tt>T*(w,t)</tt> is a final state.
95
+
96
+ This is an equivalence relation on the states +S+.
97
+
98
+ ===Dead states
99
+ We say a state +s+ is _dead_ (or a _trap_ _state_) iff for all letters +l+ <tt>T(l,s) = s</tt> and +s+ isn't a final state.
100
+
101
+ The same as for unreachable states is true for dead states. They have no influence on the generated language. Moreover each dead state is equivalent to another dead state, because both aren't final states and they don't change for a word.
102
+
103
+ ===The algorithm
104
+ 0. Eliminate all unreachable states (leaving a potential uncomplete DFA)
105
+ 1. Partitionate the set of remaining states by the equivalence relation of states. We get a set +S'+ of equivalence classes of states.
106
+ 2. Define +i'+ to be +[i]+ in +S'+.
107
+ 3. Define +F'+ to be the classes +[f]+ for each +f+ in +F+.
108
+ 4. Define <tt>T' : AxS' -> S'</tt> by <tt>T'(l,[s]) := [T(l,s)]</tt>
109
+
110
+ The DFA (A,S',i',F',T') is then a minimal DFA (but not necessarily complete anymore). If one whishes a complete minimal DFA, complete the previous DFA.
111
+
112
+ For the calculation of the partition there are several algorithms but we choose the table filling algorithm.
113
+ =end
114
+
115
+ class RLSM::DFA
116
+ =begin rdoc
117
+ This method takes as parameter a hash with following required keys:
118
+ [:+initial+] The initial state.
119
+ [:+finals+] An array of the final states.
120
+ [:+transitions+] An array of the transitions.
121
+ =end
122
+ def self.create(args = {})
123
+ args[:transitions].map! { |t| t.map { |x| x.to_s } }
124
+ args[:initial] = args[:initial].to_s
125
+ args[:finals].map! { |f| f.to_s }
126
+ args[:alphabet] = args[:transitions].map { |t| t.first }.uniq.sort
127
+ args[:states] = args[:transitions].map { |t| t[1,2] }.flatten.uniq.sort
128
+
129
+ new args
130
+ end
131
+ =begin rdoc
132
+ It is not intended to use this method directly, use create instead.
133
+
134
+ This method takes as parameter a hash with following required keys:
135
+ [:+alphabet+] An array of alphabet characters.
136
+ [:+states+] An array of states.
137
+ [:+initial+] The initial state.
138
+ [:+finals+] An array of the final states.
139
+ [:+transitions+] An array of the transitions.
140
+
141
+ It is required that all occuring states are in the states array and all transition labels are in the alphabet. Also the states and the alphabet must be unique.
142
+ =end
143
+ def initialize(args = {})
144
+ validate_presence_of_required_keys(args)
145
+
146
+ validate_uniqueness_of_alphabet_and_states(args)
147
+ validate_transition_labels_are_in_the_alphabet(args)
148
+ validate_all_states_matches(args)
149
+ validate_transition_uniquness(args)
150
+
151
+ @alphabet = args[:alphabet].sort
152
+ @states = args[:states].clone
153
+ @initial_state = args[:initial].clone
154
+ @finals = args[:finals].clone
155
+ @transitions = args[:transitions].inject({}) do |res,tr|
156
+ (res[tr.first] ||= {})[tr[1]] = tr[2]
157
+ res
158
+ end
159
+ end
160
+
161
+ attr_reader :alphabet, :states, :initial_state, :finals
162
+
163
+ #Returns the number of states.
164
+ def num_states
165
+ @states.size
166
+ end
167
+
168
+ #Returns the number of final states.
169
+ def num_finals
170
+ @finals.size
171
+ end
172
+
173
+ #Returns the state in which the DFA halts if started in state +s+ and given the word +w+. Returns +nil+ if some state doesn't accept the actual character on the way.
174
+ def [](w,s)
175
+ unless @states.include? s
176
+ raise DFAException, "Given state '#{s}' isn't a state in this DFA."
177
+ end
178
+
179
+ w.scan(/./).inject(s) do |s, c|
180
+ unless @alphabet.include? c
181
+ raise DFAException, "Given character '#{c}' isn't in the alphabet."
182
+ end
183
+ @transitions[c][s]
184
+ end
185
+ end
186
+
187
+ #Returns true if the DFA accepts +word+.
188
+ def accepts?(word)
189
+ @finals.include? self[word,@initial_state]
190
+ end
191
+
192
+
193
+ #Returns true if there exists an isomorphism between this DFA and +other+.
194
+ def isomorph_to?(other)
195
+ #Test first some required conditions
196
+ return false unless @alphabet == other.alphabet
197
+ return false unless @states.size == other.num_states
198
+ return false unless @finals.size == other.num_finals
199
+
200
+ #Find an isomorphism if possible
201
+ iso = @states.permutations.find do |p|
202
+ isomorphism?(p, other)
203
+ end
204
+
205
+ iso ? true : false
206
+ end
207
+
208
+ #Returns true if the state is reachable, raises an Exception if the state isn't in the DFA.
209
+ def reachable?(state)
210
+ raise DFAException, "Unknown state: #{state}" unless @states.include? state
211
+ (@reachable_states ||= reachable_states).include? state
212
+ end
213
+
214
+ #Returns true if given state is dead, raises an Exception if the state isn't in the DFA.
215
+ def dead?(s)
216
+ raise DFAException, "Unknown state: #{s}" unless @states.include? s
217
+
218
+ s != @initial_state and
219
+ !@finals.include?(s) and
220
+ @alphabet.all? { |l| [nil,s].include? self[l,s] }
221
+ end
222
+
223
+ #Returns true if the dfa is minimal.
224
+ def minimal?
225
+ return false unless connected?
226
+ get_equivalent_states.empty?
227
+ end
228
+
229
+ #Returns true if the DFA is complete.
230
+ def complete?
231
+ @states.all? { |s| @alphabet.all? { |l| self[l,s] } }
232
+ end
233
+
234
+ #Returns a complete DFA which generates the same language. If there exists already a dead state, this state will be used to complete the DFA.
235
+ def complete!
236
+ trap = @states.find { |s| dead?(s) }
237
+
238
+ #No dead states present? -> create one
239
+ unless trap
240
+ trap = @states.sort.last.succ
241
+ @states << trap
242
+ end
243
+
244
+ @states.each do |s|
245
+ @alphabet.each do |l|
246
+ (@transitions[l] ||= {})[s] = trap unless self[l,s]
247
+ end
248
+ end
249
+
250
+ self
251
+ end
252
+
253
+ #Returns +true+ if the DFA has no unreachable states.
254
+ def connected?
255
+ @states.all? { |s| reachable?(s) }
256
+ end
257
+
258
+ #Returns a DFA with no unreachable states.
259
+ def connect!
260
+ remove_states *@states.find_all { |s| !reachable?(s) }
261
+ self
262
+ end
263
+
264
+ #Returns +true+ if the DFA has dead states.
265
+ def dead_states?
266
+ @states.any? { |s| dead?(s) }
267
+ end
268
+
269
+ #Removes all dead states.
270
+ def remove_dead_states!
271
+ remove_states *@states.find_all { |s| dead?(s) }
272
+ self
273
+ end
274
+
275
+ =begin
276
+ Minimizes the DFA. Takes as optional parameter a hash with only processed key :+rename+. The possible values are:
277
+ [:+new+] States will be renamed to 's0', 's1', ...
278
+ [:+min+] For each class of states the state with the smallest name is taken
279
+ [:+join+] The names from each class are joined, so a class with states 'a' and 'b' will yielld a new state 'ab'. This is the default.
280
+ =end
281
+ def minimize!(opts = {})
282
+ connect!
283
+ remove_dead_states!
284
+ unless minimal?
285
+ part = state_partition
286
+ merge_states *part
287
+
288
+ case opts[:rename]
289
+ when :new : rename_states()
290
+ when :min : rename_states part.map { |cls| cls.min }
291
+ else
292
+ rename_states part.map { |cls| cls.join }
293
+ end
294
+ end
295
+
296
+ self
297
+ end
298
+
299
+ #Renames the state to the given array. Raises an exception if this array has the wrong size or the elements aren't unique.
300
+ def rename_states(arg = nil)
301
+ arg ||= (0...@states.size).map { |i| "s#{i}" }
302
+
303
+ if arg.size != @states.size
304
+ raise DFAException, "Wrong number of state names given."
305
+ end
306
+
307
+ if arg.uniq!
308
+ raise DFAException, "Given state names weren't unique."
309
+ end
310
+
311
+ rename_transitions arg
312
+
313
+ @initial_state = arg[@states.index(@initial_state)]
314
+ @finals.map! { |f| arg[@states.index(f)] }
315
+
316
+ @states = arg.clone
317
+ end
318
+
319
+ #Returns a connected copy of the DFA.
320
+ def connect
321
+ Marshal.load(Marshal.dump(self)).connect!
322
+ end
323
+
324
+ #Returns a complete copy of the DFA.
325
+ def complete
326
+ Marshal.load(Marshal.dump(self)).complete!
327
+ end
328
+
329
+ #Returns a minimized copy of the DFA.
330
+ def minimize(opts = {})
331
+ Marshal.load(Marshal.dump(self)).minimize!(opts)
332
+ end
333
+
334
+ #Returns +true+ if this DFA is equivalent to +other+
335
+ def equivalent_to?(other)
336
+ minimize.isomorph_to?(other.minimize)
337
+ end
338
+
339
+ #Returns the transition monoid of the DFA.
340
+ def transition_monoid
341
+ RLSM::Monoid.new get_binary_operation, :rename_elements => true
342
+ end
343
+
344
+ #Returns the transition monoid of the equivalent minimal DFA.
345
+ def syntactic_monoid
346
+ minimize.transition_monoid
347
+ end
348
+
349
+ #Returns a RE which represents the same language.
350
+ def to_re
351
+ les = []
352
+ les << initialize_row_for(initial_state)
353
+ (@states - [initial_state]).each do |state|
354
+ les << initialize_row_for(state)
355
+ end
356
+
357
+ #Solve for the initial state
358
+ les = update_les(les, simplify_les_row(les.pop)) until les.size == 1
359
+
360
+ simplify_les_row(les.pop)[:final]
361
+ end
362
+
363
+ private
364
+ def initialize_row_for(state)
365
+ row = { :state => state.clone }
366
+ @states.each do |s|
367
+ row[s] = RLSM::RE.new
368
+ end
369
+
370
+ @alphabet.each do |letter|
371
+ row[self[letter, state]] += RLSM::RE.new(letter) if self[letter, state]
372
+ end
373
+
374
+ if @finals.include? state
375
+ row[:final] = RLSM::RE.new(RLSM::RE::Lambda)
376
+ else
377
+ row[:final] = RLSM::RE.new
378
+ end
379
+
380
+ row
381
+ end
382
+
383
+ def update_les(l,r)
384
+ l.map do |row|
385
+ re = row[r[:state]]
386
+ @states.each do |state|
387
+ if state == r[:state]
388
+ row[state] = RLSM::RE.new
389
+ else
390
+ row[state] += re * r[state]
391
+ end
392
+ end
393
+
394
+ row[:final] += re * r[:final]
395
+ row
396
+ end
397
+ end
398
+
399
+ def simplify_les_row(row)
400
+ #Have we something like Ri = ... + xRi + ...
401
+ if row[row[:state]].pattern != ''
402
+ re = row[row[:state]].star
403
+ @states.each do |state|
404
+ if state == row[:state]
405
+ row[state] = RLSM::RE.new
406
+ else
407
+ row[state] = re * row[state]
408
+ end
409
+ end
410
+
411
+ row[:final] = re * row[:final]
412
+ end
413
+
414
+ row
415
+ end
416
+
417
+ def rename_transitions(arg)
418
+ @transitions = @transitions.to_a.inject({}) do |res,pair|
419
+ res[pair.first] = pair.last.map do |key,val|
420
+ [arg[@states.index(key)], arg[@states.index(val)]]
421
+ end.inject({}) { |r,v| r[v.first] = v.last; r }
422
+ res
423
+ end
424
+ end
425
+
426
+ def get_binary_operation
427
+ maps = []
428
+ elements = []
429
+ l = 0
430
+ loop do
431
+ new = false
432
+ each_word_of_length(l) do |w|
433
+ map = get_map(w)
434
+ unless maps.include? map
435
+ maps << map
436
+ elements << w
437
+ new = true
438
+ end
439
+ end
440
+ break unless new
441
+ l += 1
442
+ end
443
+
444
+ binop = []
445
+ (0...elements.size).to_a.product((0...elements.size).to_a).each do |i,j|
446
+ (binop[i] ||= [])[j] = maps.index(get_map(elements[i] + elements[j]))
447
+ end
448
+
449
+ binop
450
+ end
451
+
452
+ def each_word_of_length(l)
453
+ word = (@alphabet.first.to_s*l).scan(/./)
454
+ yield word.join
455
+
456
+ until word == (@alphabet.last.to_s*l).scan(/./)
457
+ word = get_next_word(word)
458
+ yield word.join
459
+ end
460
+ end
461
+
462
+ def get_next_word(word)
463
+ i = (1..word.length).find { |i| word[-i] != @alphabet.last }
464
+ word[-i] = @alphabet[@alphabet.index(word[-i])+1]
465
+ if i > 1
466
+ (1...i).each do |j|
467
+ word[-j] = @alphabet.first
468
+ end
469
+ end
470
+
471
+ word
472
+ end
473
+
474
+ def get_map(word)
475
+ @states.map { |s| self[word,s] }
476
+ end
477
+
478
+ def remove_states(*states)
479
+ @reachable_states = nil
480
+ #Remove the state
481
+ states.each do |s|
482
+ @states.delete(s)
483
+ @finals.delete(s)
484
+ end
485
+
486
+ #Remove the transitions
487
+ @alphabet.each do |l|
488
+ tr = @transitions[l].map.reject { |p| states.any? { |s| p.include? s } }
489
+ @transitions[l] = tr.inject({}) { |r,x| r[x.first] = x.last; r }
490
+ end
491
+ end
492
+
493
+ def get_renaming_scheme(classes)
494
+ classes.inject({}) do |res,cls|
495
+ state = select_state_in cls
496
+ cls.each do |x|
497
+ res[x] = state
498
+ end
499
+ res
500
+ end
501
+ end
502
+
503
+ def merge_states(*classes)
504
+ @reachable_states = nil
505
+ rename = get_renaming_scheme(classes)
506
+
507
+ @states.map! { |s| rename[s] }.uniq!
508
+ @finals.map! { |s| rename[s] }.uniq!
509
+
510
+ @alphabet.each do |l|
511
+ tr = @transitions[l].map { |x,y| [rename[x],rename[y]] }.uniq
512
+ @transitions[l] = tr.inject({}) { |r,x| r[x.first] = x.last; r }
513
+ end
514
+ end
515
+
516
+ def select_state_in(cls)
517
+ if cls.include? @initial_state
518
+ @initial_state
519
+ else
520
+ cls.min
521
+ end
522
+ end
523
+
524
+ def state_partition
525
+ eq = get_equivalent_states
526
+ @states.inject([]) do |res,s|
527
+ res << [s]
528
+ eq.find_all { |p| p.include? s }.each do |pair|
529
+ res[-1] |= pair
530
+ end
531
+
532
+ res.last.sort!
533
+ res.uniq
534
+ end
535
+ end
536
+
537
+ def set_up_table_fill_algorithm
538
+ (@states | (complete? ? [] : [nil])).unordered_pairs.partition do |x,y|
539
+ @finals.include?(x) ^ @finals.include?(y)
540
+ end
541
+ end
542
+
543
+ def table_fill_step(dis, equi)
544
+ equi.select do |x,y|
545
+ @alphabet.any? do |l|
546
+ dis.include? [@transitions[l][x], @transitions[l][y]] or
547
+ dis.include? [@transitions[l][y], @transitions[l][x]]
548
+ end
549
+ end
550
+ end
551
+
552
+ def get_equivalent_states
553
+ distinguished, equivalent = set_up_table_fill_algorithm
554
+
555
+ loop do
556
+ new_dis = table_fill_step distinguished, equivalent
557
+ break if new_dis.empty?
558
+ distinguished |= new_dis
559
+ equivalent -= new_dis
560
+ end
561
+ equivalent.reject { |p| p.include? nil }
562
+ end
563
+
564
+ def reachable_states
565
+ reachable = []
566
+ new_states = [@initial_state]
567
+
568
+ until new_states.empty?
569
+ reachable |= new_states
570
+ new_states = []
571
+ reachable.each do |s|
572
+ @alphabet.map do |l|
573
+ new_states << self[l,s] unless reachable.include? self[l,s]
574
+ end
575
+ end
576
+ new_states.compact!
577
+ end
578
+
579
+ reachable
580
+ end
581
+
582
+ def get_bijective_map_to(p, other)
583
+ @states.inject({}) do |res,s|
584
+ res[s] = other.states[p.index(s)]
585
+ res
586
+ end
587
+ end
588
+
589
+ def isomorphism?(per, o)
590
+ p = get_bijective_map_to(per, o)
591
+ return false unless o.initial_state == p[@initial_state]
592
+
593
+ unless @finals.map { |s| p[s] }.all? { |f| o.finals.include? f }
594
+ return false
595
+ end
596
+
597
+ @alphabet.product(@states).all? do |c,s|
598
+ p[self[c,s]] == o[c,p[s]]
599
+ end
600
+ end
601
+
602
+ def validate_presence_of_required_keys(args)
603
+ [:alphabet,:states,:initial,:finals,:transitions].each do |key|
604
+ raise DFAException, "No #{key} given!" unless args.key?(key)
605
+ end
606
+ end
607
+
608
+ def validate_all_states_matches(args)
609
+ unless args[:states].include? args[:initial]
610
+ raise DFAException, "Given initial state isn't in :states."
611
+ end
612
+
613
+ bad = args[:finals].find_all { |x| !args[:states].include?(x) }
614
+ unless bad.empty?
615
+ raise(DFAException,
616
+ "Given final states #{bad.inspect} aren't in :states.")
617
+ end
618
+
619
+ bad = args[:transitions].find_all do |l,x,y|
620
+ !(args[:states].include?(x) and args[:states].include?(y))
621
+ end
622
+ unless bad.empty?
623
+ raise(DFAException,
624
+ ("Given transitions #{bad.inspect}" +
625
+ "have states which aren't in :states."))
626
+ end
627
+ end
628
+
629
+ def validate_uniqueness_of_alphabet_and_states(args)
630
+ [:alphabet, :states].each do |key|
631
+ if args[key].uniq!
632
+ raise(DFAException,
633
+ "#{key} has duplicated entries: #{args[key].inspect}")
634
+ end
635
+ end
636
+ end
637
+
638
+ def validate_transition_labels_are_in_the_alphabet(args)
639
+ bad = args[:transitions].find_all { |l,x,y| !args[:alphabet].include?(l) }
640
+ unless bad.empty?
641
+ raise(DFAException,
642
+ ("Following transitions contain non alphabet characters:" +
643
+ "#{bad.inspect}."))
644
+ end
645
+ end
646
+
647
+ def validate_transition_uniquness(args)
648
+ args[:alphabet].product(args[:states]).each do |c,s|
649
+ tr = args[:transitions].find_all { |l,s1,s2| l == c and s1 == s }
650
+ if tr.size > 1
651
+ raise DFAException, ("There are multiple transitons from state " +
652
+ "#{tr.first[1]} with label #{tr.first[0]}.")
653
+ end
654
+ end
655
+ end
656
+ end