asmodis-rlsm 0.2.0

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