rlsm 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/rlsm/dfa.rb ADDED
@@ -0,0 +1,696 @@
1
+ #
2
+ # This file is part of the RLSM gem.
3
+ #
4
+ #(The MIT License)
5
+ #
6
+ #Copyright (c) 2008 Gunther Diemant <g.diemant@gmx.net>
7
+ #
8
+ #Permission is hereby granted, free of charge, to any person obtaining
9
+ #a copy of this software and associated documentation files (the
10
+ #'Software'), to deal in the Software without restriction, including
11
+ #without limitation the rights to use, copy, modify, merge, publish,
12
+ #distribute, sublicense, and/or sell copies of the Software, and to
13
+ #permit persons to whom the Software is furnished to do so, subject to
14
+ #the following conditions:
15
+ #
16
+ #The above copyright notice and this permission notice shall be
17
+ #included in all copies or substantial portions of the Software.
18
+ #
19
+ #THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
20
+ #EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ #MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22
+ #IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23
+ #CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24
+ #TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25
+ #SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+ #
27
+
28
+
29
+ require File.join(File.dirname(__FILE__), 'monkey_patching')
30
+ require File.join(File.dirname(__FILE__), 'monoid')
31
+ require File.join(File.dirname(__FILE__), 'regexp')
32
+
33
+ module RLSM
34
+ class DFA
35
+ def initialize(alph, states, initial, finals, transitions)
36
+ @alphabet = alph.sort
37
+
38
+ #State identifiers should be unique and shouldn't be trap
39
+ if states.uniq != states or states.include? 'trap'
40
+ raise DFAException, "Bad states. State names must be unique and not 'trap'"
41
+ end
42
+
43
+ #Create the states
44
+ @states = states.map do |state|
45
+ if state == initial
46
+ if finals.include? state
47
+ State.new(self, state.to_s, :initial => true, :final => true)
48
+ else
49
+ State.new(self, state.to_s, :initial => true)
50
+ end
51
+ elsif finals.include? state
52
+ State.new(self, state.to_s, :final => true)
53
+ else
54
+ State.new(self, state.to_s)
55
+ end
56
+ end
57
+
58
+ #Add the transitions and check for completness
59
+ @states.each do |state|
60
+ transitions.select { |c, s1, s2| s1.to_s == state.label }.each do |c, s1, s2|
61
+ state.add_transition c, _get_state(s2.to_s)
62
+ end
63
+ end
64
+
65
+ #Calculate the reachable states
66
+ @reachable_states = [initial_state]
67
+ changed = true
68
+
69
+ while changed
70
+ changed = false
71
+ (@states - @reachable_states).each do |state|
72
+ if @reachable_states.any? { |s| s.reachable_states.include? state }
73
+ @reachable_states << state
74
+ changed = true
75
+ end
76
+ end
77
+ end
78
+
79
+ #Bring the initial state to index 0
80
+ ii = @states.index(initial_state)
81
+ if ii != 0
82
+ @states[0], @states[ii] = @states[ii], @states[0]
83
+ end
84
+ end
85
+
86
+ attr_reader :alphabet, :reachable_states
87
+
88
+ #Returns the initial state
89
+ def initial_state
90
+ @initial ||= @states.find { |state| state.initial? }.deep_copy
91
+ end
92
+
93
+ #Returns an array of all final states.
94
+ def final_states
95
+ @finals ||= @states.find_all { |state| state.final? }.deep_copy
96
+ end
97
+
98
+ #Returns an array of all states.
99
+ def states
100
+ @states.deep_copy
101
+ end
102
+
103
+ #The number of states
104
+ def num_states
105
+ @states.size
106
+ end
107
+
108
+ #The number of final states.
109
+ def num_finals
110
+ final_states.size
111
+ end
112
+
113
+ #Returns the state in which the DFA halts if started in state and given str. Returns nil if this string isn't parseable by the DFA started in state. If the state is omitted, default start state is the initial state (surprising, isn't it).
114
+ def process(str, state = nil)
115
+ s = state || initial_state
116
+ str.each_char do |char|
117
+ s = s.process(char)
118
+ return nil if s.nil?
119
+ end
120
+
121
+ s
122
+ end
123
+
124
+ #Returns true if the given string is accepted by the DFA.
125
+ def accepts?(str)
126
+ s = process(str)
127
+ #Full String parsed, are we now in a final state or was an error?
128
+ s ? s.final? : false
129
+ end
130
+
131
+ #Returns an array of states. The state at position i is the state in which the DFA halts, if started in states[i] and given str.
132
+ #
133
+ #Caution: If a state can't process str, for this state nil is returned. In a complete DFA for any valid input string, a non nil State will be returnd.
134
+ def transition_function(str)
135
+ @states.map { |state| process(str, state) }
136
+ end
137
+
138
+ #Returns true if a DFA isomorphism between self and other exists.
139
+ def isomorph_to?(other)
140
+ #First a few necessary conditions
141
+ return false if num_states != other.num_states
142
+ return false if num_finals != other.num_finals
143
+ return false if alphabet != other.alphabet
144
+
145
+ initial_index = @states.index(initial_state)
146
+ final_indices = final_states.map { |f| @states.index(f) }
147
+ (0...num_states).to_a.permutations.each do |per|
148
+ #Initial state must map to initial state
149
+ next unless other.states[per[initial_index]].initial?
150
+
151
+ #Finals must map to finals
152
+ next unless final_indices.all? { |fi| other.states[fi].final? }
153
+
154
+ #Transactions respected?
155
+ bad = @states.find do |state|
156
+ not @alphabet.all? do |char|
157
+ si = @states.index(state)
158
+ ps = state.process(char)
159
+
160
+ if ps.nil?
161
+ other.states[per[si]].process(char).nil?
162
+ else
163
+ os = other.states[per[si]].process(char)
164
+ if os.nil?
165
+ false
166
+ else
167
+ per[@states.index(ps)] == other.states.index(os)
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ #Found an iso, i.e. no bad states?
174
+ return true unless bad
175
+ end
176
+
177
+ #No isomorphism found
178
+ return false
179
+ end
180
+
181
+ #A synonym for isomorph_to?
182
+ def ==(other)
183
+ isomorph_to? other
184
+ end
185
+
186
+ #Returns true, if the two DFAs accepts the same language, i.e. the mimimized DFAs are isomorph.
187
+ def equivalent_to?(other)
188
+ minimze == other.minimize
189
+ end
190
+
191
+ #Returns true, if the two DFAs accepts languages of the same structure, i.e. if the languages differs only in the used alphabet. For example the languages aab and bba are similar.
192
+ def similar_to?(other)
193
+ dfa1 = minimize
194
+ dfa2 = other.minimize
195
+
196
+ #First a few necessary conditions
197
+ return false if dfa1.num_states != dfa2.num_states
198
+ return false if dfa1.num_finals != dfa2.num_finals
199
+
200
+ dfa1.alphabet.permutations do |per|
201
+ #Get the states
202
+ states = other.states.map { |st| st.label }
203
+
204
+ #Get the initial
205
+ initial = other.initial_state.label
206
+
207
+ #Get the finals
208
+ finals = other.final_states.map { |fs| fs.label }
209
+
210
+ #Get the transitions
211
+ trans = []
212
+ other.states.each do |s|
213
+ s.transitions.each_pair { |c,os| trans << [c,s.label, os.label].dup }
214
+ end
215
+
216
+ #Alter the transitions wrt to the permutation
217
+ trans.map! do |c,s1,s2|
218
+ [per[other.alphabet.index(c)],s1,s2]
219
+ end
220
+
221
+ #Exisits now an isomorphism between self and the new dfa?
222
+ return true if self == new(@alphabet, states, initial, finals, trans)
223
+ end
224
+
225
+ #No similarity found
226
+ return false
227
+ end
228
+
229
+ #Returns a minimal DFA which accepts the same language (see minimize!)
230
+ def minimize(opts = {})
231
+ self.deep_copy.minimize!(opts)
232
+ end
233
+
234
+ #Alters the DFA to a minimal one which accepts the same language.
235
+ #If the DFA is complete, then the minimal DFA returned is also complete, i.e. if there is a trap state (a dead state, but without, the DFA isn't complete), it will not be deleted. To do so, call remove_dead_states after minimizing.
236
+ #If passed :rename_states => true, the state labels will be renamed to something short (propably 0,1,2,...).
237
+ def minimize!(opts = {})
238
+ #First step: remove unreachable states
239
+ remove_unreachable_states!
240
+
241
+ complete_temp = complete?
242
+
243
+ #Second step: Find all equivalent states
244
+ #Create the initial state partition
245
+ sp = @states.partition { |state| state.final? }
246
+ sp_labels = sp.map { |sc| sc.map {|st| st.label} }.sort
247
+
248
+ #Calculate the new state partition for the first time
249
+ nsp = new_state_partition(sp)
250
+ nsp_labels = nsp.map { |sc| sc.map {|st| st.label} }.sort
251
+
252
+ #Find all state classes (repeating process until nothing changes)
253
+ while sp_labels != nsp_labels
254
+ sp, nsp = nsp.deep_copy, new_state_partition(nsp)
255
+
256
+ sp_labels = sp.map { |sc| sc.map {|st| st.label} }.sort
257
+ nsp_labels = nsp.map { |sc| sc.map {|st| st.label} }.sort
258
+ end
259
+
260
+ #Third step: Are we done?
261
+ #Check if the DFA was already minimal
262
+ return self if sp.all? { |sc| sc.size == 1 }
263
+
264
+ #Fourth step: Constructing the new DFA:
265
+ #1 the states
266
+ @states = sp.map do |sc|
267
+ state_label = sc.map { |s| s.label }.join
268
+ if sc.include? initial_state
269
+ if final_states.any? {|f| sc.include? f }
270
+ State.new(self, state_label, :initial => true, :final => true)
271
+ else
272
+ State.new(self, state_label, :initial => true)
273
+ end
274
+ elsif final_states.any? { |f| sc.include? f }
275
+ State.new(self, state_label, :final => true)
276
+ else
277
+ State.new(self, state_label)
278
+ end
279
+ end
280
+
281
+ #2 the transitions
282
+ @states.each_with_index do |state, sc_index|
283
+ sp[sc_index].first.transitions.each_pair do |char, s2|
284
+ state.add_transition char, @states[_class_index_of(sp,s2)]
285
+ end
286
+ end
287
+
288
+
289
+ #3 delete dead states
290
+ remove_dead_states!(:except_trap => complete_temp)
291
+
292
+ #4 Force recalculation of initial and final states
293
+ @initial, @finals = nil, nil
294
+
295
+ #Bring the initial state to index 0
296
+ ii = @states.index(initial_state)
297
+ if ii != 0
298
+ @states[0], @states[ii] = @states[ii], @states[0]
299
+ end
300
+
301
+
302
+ #5 Was renaming of the states requested?
303
+ if opts[:rename_states]
304
+ @states.each_with_index do |state,index|
305
+ state.label = index.to_s
306
+ end
307
+ end
308
+
309
+ #6 update the reachable states (all are reachable in a minimal DFA)
310
+ @reachable_states = @states.deep_copy
311
+
312
+
313
+ self
314
+ end
315
+
316
+ #Returns true if the DFA is minimal (see minimize!).
317
+ def minimal?
318
+ num_states == minimize.num_states
319
+ end
320
+
321
+ #Returns a complete DFA which accepts the same language.
322
+ def complete
323
+ self.deep_copy.complete!
324
+ end
325
+
326
+ #Adds a dead state and adds to every other state a transition to this state for all missing alphabet elements. If the DFA is already complete nothing happens.
327
+ #
328
+ #In either case the complete DFA is returned.
329
+ def complete!
330
+ #Is work to do?
331
+ return self if complete?
332
+
333
+ #Create the trap state
334
+ trap = State.new(self, 'trap')
335
+ @alphabet.each do |char|
336
+ trap.add_transition char, trap
337
+ end
338
+
339
+ #Add the necassery transitions
340
+ @states.each do |state|
341
+ unless state.complete?
342
+ (@alphabet - state.accepted_chars).each do |char|
343
+ state.add_transition char, trap
344
+ end
345
+ end
346
+ end
347
+
348
+ #Add the trap state to the DFA
349
+ @states << trap
350
+ @reachable_states << trap
351
+
352
+ self
353
+ end
354
+
355
+ #Returns true if this DFA is complete, i.e. all states accepts all alphabet symbols.
356
+ def complete?
357
+ @states.all? do |state|
358
+ state.complete?
359
+ end
360
+ end
361
+
362
+ #Returns an array of dead states (a state is dead, if it is not final and has outdegree 0)
363
+ def dead_states
364
+ @states.find_all { |state| state.dead? }
365
+ end
366
+
367
+ #Returns true if the DFA has dead states (see also dead_states)
368
+ def dead_states?
369
+ @states.find { |state| state.dead? } ? true : false
370
+ end
371
+
372
+ #Removes all dead_states. If passed :except_trap => true, trap states wouldn't be removed.
373
+ def remove_dead_states!(opt = {})
374
+ @states.each do |state|
375
+ state.remove_transitions_to_dead_states(opt)
376
+ end
377
+
378
+ #Remove the states
379
+ if opt[:except_trap]
380
+ @states = @states.reject do |state|
381
+ state.dead? and not state.trap?
382
+ end
383
+ @reachable_states = @reachable_states.reject do |state|
384
+ state.dead? and not state.trap?
385
+ end
386
+ else
387
+ @states = @states.reject { |state| state.dead? }
388
+ @reachable_states = @reachable_states.reject { |state| state.dead? }
389
+ end
390
+
391
+ self
392
+ end
393
+
394
+ #Returns a copy with dead states removed.
395
+ def remove_dead_states(opt = {})
396
+ self.deep_copy.remove_dead_states!(opt)
397
+ end
398
+
399
+ #Returns an array of states, wich aren't reachable from the initial state.
400
+ def unreachable_states
401
+ @states.find_all { |state| state.unreachable?}
402
+ end
403
+
404
+ #Returns true if the DFA has unreachable states
405
+ def unreachable_states?
406
+ @states.find { |state| state.unreachable? } ? true : false
407
+ end
408
+
409
+ #Removes all unreachable states.
410
+ def remove_unreachable_states!
411
+ #No transition update necessary, because each state which reaches an unreachble state must be unreachable.
412
+ @states = @states.reject { |state| state.unreachable? }
413
+ @reachable_states = @states.deep_copy
414
+
415
+ self
416
+ end
417
+
418
+ #Returns a copy with unreachable states removed.
419
+ def remove_unreachable_states
420
+ self.deep_copy.remove_unreachable_states!
421
+ end
422
+
423
+ #Returns an RLSM::RegExp instance representing the same language as this DFA.
424
+ def to_regexp
425
+ #No finals => no language
426
+ return RLSM::RegExp.new if final_states.empty?
427
+
428
+ #Calculate the coeffizients matrix
429
+ #1 empty matrix with lambdas for final states
430
+ matr = @states.map do |st|
431
+ last = st.final? ? [RLSM::RegExp.new('&')] : [RLSM::RegExp.new]
432
+ [RLSM::RegExp.new]*num_states + last
433
+ end
434
+
435
+ #2 remaining coeffizients
436
+ @states.each_with_index do |state,i|
437
+ state.transitions.each_pair do |ch, st|
438
+ matr[i][@states.index(st)] += RLSM::RegExp.new(ch)
439
+ end
440
+ end
441
+
442
+ #Solve the matrix for matr[0][0] (Remember 0 is index of initial state)
443
+ (num_states-1).downto 1 do |i|
444
+ #Depends Ri on itself? If so apply well known simplify rule
445
+ # R = AR +B -> R = A*B
446
+ unless matr[i][i].empty?
447
+ matr[i].map! { |re| matr[i][i].star * re }
448
+ matr[i][i] = RLSM::RegExp.new
449
+ end
450
+
451
+ #Substitute know Ri in everey row above
452
+ ri = matr.pop
453
+
454
+ matr.map! do |row|
455
+ row.each_with_index do |re,j|
456
+ row[j] = re + ri[j]
457
+ end
458
+
459
+ row[i] = RLSM::RegExp.new
460
+
461
+ row
462
+ end
463
+ end
464
+
465
+ #Examine now the last remaining first row (irritating...)
466
+ regexp = matr.pop
467
+
468
+ if regexp[0].empty? #R0 depends not on R0
469
+ return regexp.last
470
+ else #R0 depends on R0
471
+ return regexp[0].star * regexp.last
472
+ end
473
+ end
474
+
475
+ #Calculate the transition monoid of the DFA. Because it is only possible for an complete DFA to compute the TM, the TM is calculated for the DFA returned by complete.
476
+ def transition_monoid
477
+ dfa = self.complete
478
+
479
+ #Calculate the monoid elements
480
+ trans_tab = [["", dfa.transition_function("")]]
481
+
482
+ new_elements = true
483
+ str_length = 1
484
+ while new_elements
485
+ new_elements = false
486
+ dfa.each_str_with_length(str_length) do |str|
487
+ tf = dfa.transition_function(str)
488
+ unless trans_tab.map { |s,f| f}.include? tf
489
+ trans_tab << [str, tf]
490
+ new_elements = true
491
+ end
492
+ end
493
+ str_length += 1
494
+ end
495
+
496
+ #Calculate the binary operation
497
+ binop = [(0...trans_tab.size).to_a]
498
+
499
+ (1...trans_tab.size).each do |i|
500
+ str = trans_tab[i].first
501
+ binop << trans_tab.map do |s, tf|
502
+ trans_tab.map {|st,f| f }.index(dfa.transition_function(str + s))
503
+ end
504
+ end
505
+
506
+
507
+ RLSM::Monoid.new binop.map { |row| row.join(',') }.join(' ')
508
+ end
509
+
510
+ #Returns the syntactic monoid which belongs to the language accepted by the DFA. (In fact, the transition monoid of the minimal DFA is returned, both monoids are isomorph)
511
+ def syntactic_monoid
512
+ minimize.transition_monoid
513
+ end
514
+
515
+ #Returns a string represantation
516
+ def to_s
517
+ @states.map { |state| state.to_s }.join("\n")
518
+ end
519
+
520
+
521
+ protected
522
+ def each_str_with_length(length)
523
+ if length == 0
524
+ yield ""
525
+ return
526
+ end
527
+
528
+ str = [@alphabet.first.clone]*length
529
+ pos = length-1
530
+ finished = false
531
+ loop do
532
+ yield str.join
533
+
534
+ loop do
535
+ if str[pos] == @alphabet.last
536
+ if pos == 0
537
+ finished = true
538
+ break
539
+ end
540
+ str[pos] = @alphabet.first
541
+ pos -= 1
542
+ else
543
+ str[pos] = @alphabet[@alphabet.index(str[pos])+1]
544
+ pos += 1 unless pos == length-1
545
+ break
546
+ end
547
+ end
548
+
549
+ break if finished
550
+ end
551
+ end
552
+
553
+ private
554
+ def _get_state(label)
555
+ @states.find { |state| state.label == label }
556
+ end
557
+
558
+ def _class_index_of(sp, s)
559
+ sp.each_with_index { |sc,i| return i if sc.include? s }
560
+ end
561
+
562
+ def new_state_partition(sp)
563
+ res = []
564
+ sp.each do |set|
565
+ partitionate_set(set, sp).compact.each do |s|
566
+ res << s
567
+ end
568
+ end
569
+
570
+ res
571
+ end
572
+
573
+ def partitionate_set(s, p)
574
+ #A singelton set is already partitionated
575
+ return s if s.empty?
576
+
577
+ acc_chars = s.first.accepted_chars.sort
578
+
579
+ #Find the classes in which the first element of s is transitionated
580
+ classes = {}
581
+
582
+ acc_chars.each do |c|
583
+ classes[c] = p.find { |set| set.include? s.first.process(c) }
584
+ end
585
+
586
+ first_class = s.find_all do |x|
587
+ acc_chars.all? { |c| classes[c].include? x.process(c) } and
588
+ acc_chars == x.accepted_chars.sort
589
+ end
590
+
591
+ rest = s - first_class
592
+
593
+ [first_class, *partitionate_set(rest, p)]
594
+ end
595
+ end
596
+
597
+ class DFA::State
598
+ def initialize(dfa, label, opts = {})
599
+ @label = label
600
+ @dfa = dfa
601
+ @is_initial = opts[:initial] || false
602
+ @is_final = opts[:final] || false
603
+
604
+ @trans = {}
605
+ end
606
+
607
+ attr_accessor :label
608
+
609
+ def initial?
610
+ @is_initial
611
+ end
612
+
613
+ def final?
614
+ @is_final
615
+ end
616
+
617
+ #Return true if this state is dead. A state is dead if there is know edge leading to another state and the state himself isn't a final state.
618
+ def dead?
619
+ (@trans.empty? or reachable_states == [self]) and not final?
620
+ end
621
+
622
+ #Return true if this state is a trap state. A trap state is a dead_state which accepts all alphabet elements and is reachable. A trap state is useful if a complete DFA is requested.
623
+ def trap?
624
+ dead? and reachable? and accepted_chars.sort == @dfa.alphabet.sort
625
+ end
626
+
627
+ def add_transition(char, dest_state)
628
+ if @trans.key? char
629
+ raise DFAException, "Have already a transition for #{char}"
630
+ end
631
+
632
+ @trans[char] = dest_state
633
+ end
634
+
635
+ def process(char)
636
+ if @trans.key? char
637
+ @trans[char]
638
+ else
639
+ nil
640
+ end
641
+ end
642
+
643
+ def complete?
644
+ @trans.size == @dfa.alphabet.size
645
+ end
646
+
647
+ def accepted_chars
648
+ @trans.keys
649
+ end
650
+
651
+ def reachable_states
652
+ @trans.values
653
+ end
654
+
655
+ def transitions
656
+ @trans
657
+ end
658
+
659
+ def remove_transitions_to_dead_states(opt = {})
660
+ if opt[:except_trap]
661
+ @trans = @trans.reject do |char, state|
662
+ state.dead? and not state.trap?
663
+ end
664
+ else
665
+ @trans = @trans.reject { |char, state| state.dead? }
666
+ end
667
+ end
668
+
669
+ def reachable?
670
+ @dfa.reachable_states.include? self
671
+ end
672
+
673
+ def unreachable?
674
+ not reachable?
675
+ end
676
+
677
+ def to_s
678
+ str = ''
679
+ str += '-> ' if initial?
680
+ str += '* ' if final?
681
+ str += @label.to_s + ': '
682
+ str += @trans.to_a.map { |c,s| c+' -> '+s.label.to_s }.join('; ')
683
+
684
+ str
685
+ end
686
+
687
+ def ==(other)
688
+ return false if other.class != DFA::State
689
+ return false if label != other.label
690
+
691
+ @trans.all? do |char, state|
692
+ state.label == other.process(char).label
693
+ end
694
+ end
695
+ end
696
+ end