rlsm 1.0.0 → 1.1.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.
@@ -1,17 +1,13 @@
1
- $:.unshift File.expand_path(File.dirname(__FILE__))
2
-
3
- require 'monkey_patching/array_ext'
4
-
5
1
  module RLSM
6
- VERSION = '1.0.0'
7
- end
2
+ VERSION = "1.1.0"
8
3
 
9
- #Setting up the exception classes.
10
- class RLSMException < Exception; end
11
- class MonoidException < RLSMException; end
12
- class DFAException < RLSMException; end
13
- class REException < RLSMException; end
4
+ def self.lib_path_to(file_name)
5
+ File.join(File.dirname(__FILE__), 'rlsm', file_name)
6
+ end
14
7
 
15
- require 'rlsm/monoid'
16
- require 'rlsm/dfa'
17
- require 'rlsm/re'
8
+ autoload :BinaryOperation, RLSM.lib_path_to("binary_operation.rb")
9
+ autoload :Monoid, RLSM.lib_path_to("monoid.rb")
10
+ autoload :DFA, RLSM.lib_path_to("dfa.rb")
11
+ autoload :RegExp, RLSM.lib_path_to("regexp.rb")
12
+ end
13
+
@@ -0,0 +1,151 @@
1
+ require File.join(File.dirname(__FILE__), 'helper')
2
+ RLSM.require_extension 'binop'
3
+
4
+ module RLSM
5
+ class BinaryOperation
6
+ class << self; alias original_new new; end
7
+ =begin rdoc
8
+ Creates a BinaryOperation from given description and validates the result.
9
+ Raises a ParseError if validation fails.
10
+
11
+ The description is of the form
12
+ [<elements>:]<table>
13
+ where the optional <elements> part lists the elements on which the binary
14
+ operation acts in the order in which they appear in the table part.
15
+ The elements are seperated by commas. The commas are optional if each
16
+ element is identified by a single letter. no withespaces are allowed in
17
+ an element identifier.
18
+
19
+ The table part describes the transition table of the binary operation.
20
+ Rows are seperated by at least one whitespace character and elements in
21
+ a row are seperated by commas. The commas are optional if each element
22
+ is identified by a single letter.
23
+ If commas are used in one row to seperate the elements, one must use
24
+ commas in each row.
25
+
26
+ *Remark*: If the elements part is omitted it is assumed that
27
+ the elements are appearing in the right order and all elements
28
+ are appearing. This is the case for a monoid with neutral element
29
+ in the first row.
30
+
31
+ *Examples*
32
+ 012 120 201
33
+ 012:000 000 000
34
+ x1,x2,x3: x1,x2,x2 x2,x2,x1 x3,x2,x1
35
+ 000 000 000 -> error
36
+ 0,1,2 120 2,1,0 -> error
37
+ =end
38
+ def self.new(description)
39
+ binop = new!(description)
40
+ validate(binop)
41
+
42
+ binop
43
+ end
44
+
45
+ #Like new, but without validation of the input.
46
+ def self.new!(description)
47
+ original_new *parse(description)
48
+ end
49
+
50
+ #:call-seq:
51
+ # original_new(table,elements,mapping) -> BinaryOperation
52
+ #
53
+ #Creates a BinaryOperation. The arguments are a table an array with elements
54
+ #and a mapping.
55
+ #*Remark*: Use this method only if you know what you're doing. No
56
+ #validation or argument checking is made. Its primary porpose is to speed up
57
+ #monoid generation in the RLSM::Monoid.each method.
58
+ def initialize(table, elements, mapping)
59
+ @table, @elements, @mapping = table, elements, mapping
60
+ @order = @mapping.size
61
+ end
62
+
63
+ #Internal representation of the table.
64
+ attr_reader :table
65
+
66
+ #Array with all different elements
67
+ attr_reader :elements
68
+
69
+ #The number of elements.
70
+ attr_reader :order
71
+
72
+ #Maps element names to numbers.
73
+ attr_reader :mapping
74
+
75
+ #Calculate the product of +x+ and +y+.
76
+ #
77
+ #If at least one of the given elements is unknown raises an ArgumentError.
78
+ def [](x, y)
79
+ if @mapping[x].nil?
80
+ raise ArgumentError, "Unknown element #{x}"
81
+ elsif @mapping[y].nil?
82
+ raise ArgumentError, "Unknown element #{y}"
83
+ else
84
+ @elements[@table[@mapping[x]*@order + @mapping[y]]]
85
+ end
86
+ end
87
+
88
+ #Checks if binary operation is associative.
89
+ def associative?
90
+ !non_associative_triple
91
+ end
92
+
93
+ #Checks if binary operation is commutative.
94
+ def commutative?
95
+ is_commutative
96
+ end
97
+
98
+ #Checks if the binary operation is associative. If not raises an BinOpError.
99
+ def enforce_associativity
100
+ nat = non_associative_triple
101
+
102
+ unless nat.nil?
103
+ err_str = "(#{nat[0]}#{nat[0]})#{nat[0]} != #{nat[0]}(#{nat[1]}#{nat[2]})"
104
+ raise BinOpError, "Associativity required, but #{err_str}."
105
+ end
106
+ end
107
+
108
+ private
109
+ def self.validate(binop)
110
+ if binop.table.size == 0
111
+ raise ArgumentError, "No elements given."
112
+ end
113
+
114
+ if binop.table.size != binop.mapping.size**2
115
+ raise ParseError, "Either to many elements or wrong table format."
116
+ end
117
+ end
118
+
119
+ def self.parse(description)
120
+ mapping = {}
121
+ elements = []
122
+ table = []
123
+ if description =~ /\s*:\s*/
124
+ elements = desc2ary(Regexp.last_match.pre_match)
125
+ elements.each_with_index { |el,i| mapping[el] = i }
126
+
127
+ desc2ary(Regexp.last_match.post_match).
128
+ each { |el| table << mapping[el] }
129
+ else
130
+ desc2ary(description).each do |element|
131
+ if mapping[element].nil?
132
+ mapping[element] = elements.size
133
+ elements << element
134
+ end
135
+
136
+ table << mapping[element]
137
+ end
138
+ end
139
+
140
+ [table, elements, mapping]
141
+ end
142
+
143
+ def self.desc2ary(str)
144
+ if str.include?(',')
145
+ str.gsub(/^\s+/,'').gsub(/,*\s+,*/,',').split(',')
146
+ else
147
+ str.gsub(/\s+/,'').scan(/./)
148
+ end
149
+ end
150
+ end
151
+ end
@@ -1,717 +1,533 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), '..', 'rlsm'))
2
-
3
- =begin rdoc
4
- ==Basic theory of deterministic finite automaton (short DFA)
5
- ===Terminology
6
- We will call a finite nonempty set an _alphabet_ and we will call an element of an alphabet a _letter_.
7
-
8
- 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...'.
9
-
10
- ===Definition of a DFA
11
- A DFA is a five tuple <tt>(A,S,i,F,T)</tt> where
12
- * +A+ is an alphabet,
13
- * +S+ is a nonempty set,
14
- * +i+ is an element of +S+,
15
- * +F+ is a subset of +S+ and
16
- * <tt>T:AxS -> S</tt> is a function.
17
-
18
- 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.
19
-
20
-
21
- In the following we will define some properties of a DFA and of states which will be of some interest:
22
-
23
- ===The generalized transition function +T*+
24
- Given a DFA, a generalized version of the transition function <tt>T*: A*xS -> S</tt> can be recursivly defined by
25
- T*('',s) := s
26
- T*('a',s) := T(a,s)
27
- T*('abcd...',s) := T*('bcd...',T(a,s))
28
-
29
- It isn't really necessary to define +T*+, but it helps to define further properties in a concise way.
30
-
31
- ===Isomorphic DFAs
32
- Like always we want to describe in what cases we consider two DFAs to be the same:
33
-
34
- 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
35
- 1. <tt>A = A'</tt>
36
- 2. <tt>I(i) = i'</tt>
37
- 3. For all +f+ in +F+ it is +I(f)+ in +F'+
38
- 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>
39
-
40
- 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.
41
-
42
- ===The language of a DFA
43
- Let +M+ be a DFA. The subset of +A*+ defined by
44
- L(M) := { w in A* | T*(w,i) is a final state }
45
- is a formal language, in fact it is a _regular_ _language_. It is called the _language_ _generated_ _by_ +M+.
46
-
47
- 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>.
48
-
49
- It is easy to see, that this relation is an equivalence relation and we can define the equivalence class of a DFA +M+:
50
- [M] := { N | N is a DFA which is equivalent to M }
51
-
52
- ===The minimal DFA of a regular language
53
- 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
54
- N < M :<=> N has less states than M
55
- 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+.
56
-
57
- It has been shown that two minimal DFAs for a language +L+ are isomorphic. So we can talk about _the_ minimal DFA for +L+.
58
-
59
- ==An algorithm to find a minimal DFA
60
- 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)+.
61
-
62
- Because we know, that some states have no influence on the generated language, we must find some criterias which states aren't required.
63
-
64
- ===Unreachable states
65
- 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.
66
-
67
- 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.
68
-
69
- 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
70
-
71
- ===Completeness of a DFA
72
- 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+.
73
-
74
- 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.
75
-
76
- ====An algorithm to complete a DFA
77
- If we have a noncomplete DFA <tt>(A,S,I,F,T)</tt> we can complete it in the following way:
78
- 1. Add a _trap_ _state_ +t+.
79
- 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.
80
- Now the DFA <tt>(A,Su{t},I,F,T')</tt> is complete.
81
-
82
- ====Remark
83
- 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
84
- D(T*) := { (w,s) | w in A*, F(w,s) != t }
85
- and
86
- T* := F|D(T*)
87
- Now +T*+ is a generalized transition function for a noncomplete DFA.
88
-
89
- Also the completion doesn't change the generated language.
90
-
91
- ===Equivalent states
92
- 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.
93
-
94
- This is an equivalence relation on the states +S+.
95
-
96
- ===Dead states
97
- 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.
98
-
99
- 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.
100
-
101
- ===The algorithm
102
- 0. Eliminate all unreachable states (leaving a potential uncomplete DFA)
103
- 1. Partitionate the set of remaining states by the equivalence relation of states. We get a set +S'+ of equivalence classes of states.
104
- 2. Define +i'+ to be +[i]+ in +S'+.
105
- 3. Define +F'+ to be the classes +[f]+ for each +f+ in +F+.
106
- 4. Define <tt>T' : AxS' -> S'</tt> by <tt>T'(l,[s]) := [T(l,s)]</tt>
107
-
108
- 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.
109
-
110
- For the calculation of the partition there are several algorithms but we choose the table filling algorithm.
111
- =end
112
-
113
- class RLSM::DFA
114
- =begin rdoc
115
- This method takes as parameter a hash with following required keys:
116
- [:+initial+] The initial state.
117
- [:+finals+] An array of the final states.
118
- [:+transitions+] An array of the transitions.
119
- =end
120
- def self.create(args = {})
121
- args[:transitions].map! { |t| t.map { |x| x.to_s } }
122
- args[:initial] = args[:initial].to_s
123
- args[:finals].map! { |f| f.to_s }
124
- args[:alphabet] = args[:transitions].map { |t| t.first }.uniq.sort
125
- args[:states] = args[:transitions].map { |t| t[1,2] }.flatten.uniq.sort
126
-
127
- new args
128
- end
129
- =begin rdoc
130
- It is not intended to use this method directly, use create instead.
131
-
132
- This method takes as parameter a hash with following required keys:
133
- [:+alphabet+] An array of alphabet characters.
134
- [:+states+] An array of states.
135
- [:+initial+] The initial state.
136
- [:+finals+] An array of the final states.
137
- [:+transitions+] An array of the transitions.
138
-
139
- 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.
140
- =end
141
- def initialize(args = {})
142
- validate_presence_of_required_keys(args)
143
-
144
- validate_uniqueness_of_alphabet_and_states(args)
145
- validate_transition_labels_are_in_the_alphabet(args)
146
- validate_all_states_matches(args)
147
- validate_transition_uniquness(args)
1
+ require File.join(File.dirname(__FILE__), 'helper')
2
+ require File.join(File.dirname(__FILE__), 'regexp')
3
+ require File.join(File.dirname(__FILE__), 'monoid')
4
+ RLSM::require_extension('array')
5
+
6
+ require "strscan"
7
+
8
+ module RLSM
9
+ class DFA
10
+ #Synonym for new.
11
+ def self.[](description)
12
+ new(description)
13
+ end
14
+
15
+ #Creates a new DFA. The +description+ is a string which describes states and transitions.
16
+ #A state is described by a descriptor, which consists of latin letters and numbers (no whitespaces).
17
+ #
18
+ #To indicate a final state, the state descriptor starts with a *. This is allowed multiple times, even for the same state.
19
+ #
20
+ #The initial state starts with a right brace ('}'). This is only allowed once.
21
+ #
22
+ #A transition is described as follows
23
+ # STATE -LETTERLIST-> STATE
24
+ # were +STATE+ is a state descriptor (* and } modifiers allowed) and +LETTERLIST+ is either a single letter of the alphabet or a comma seperated list of alphabet letters.
25
+ #
26
+ #*Remark*: This desription format may be the worst design desicion in the whole gem ...
27
+ #
28
+ #*Example*:
29
+ # RLSM::DFA["}s1-a,b->*s2"]
30
+ # RLSM::DFA["}s1 s2 *s3 s1-a->s2 s2-a,b->*s3 s3-b->s1"]
31
+ # RLSM::DFA["}s1 s2 *s3 }s1-a->s2 s2-a,b->*s3 s3-b->s1"] # => Error
32
+ def initialize(description)
33
+ parse_initial_state(description)
34
+ parse_states(description)
35
+ parse_final_states(description)
36
+ parse_transitions(description)
37
+ end
38
+
39
+ #Initial state of the DFA.
40
+ attr_reader :initial_state
41
+
42
+ #Array of accepting states.
43
+ attr_reader :final_states
44
+
45
+ #Array of all states
46
+ attr_reader :states
47
+
48
+ #The alphabet of the DFA.
49
+ attr_reader :alphabet
50
+
51
+ #Returns array of transitions (a transition is a triple consisting of start, destination and label).
52
+ def transitions
53
+ return [] if @transitions.nil?
54
+
55
+ result = []
56
+ @transitions.each do |state,labels|
57
+ labels.each do |label,dest|
58
+ result << [state,dest,label]
59
+ end
60
+ end
148
61
 
149
- @alphabet = args[:alphabet].sort
150
- @states = args[:states].clone
151
- @initial_state = args[:initial].clone
152
- @finals = args[:finals].clone
153
- @transitions = args[:transitions].inject({}) do |res,tr|
154
- (res[tr.first] ||= {})[tr[1]] = tr[2]
155
- res
62
+ result
156
63
  end
157
- end
158
64
 
159
- attr_reader :alphabet, :states, :initial_state, :finals
160
-
161
- #Returns an Array with 3-tuples as transitions.
162
- def transitions
163
- res = []
164
- @alphabet.each do |letter|
165
- res |= @transitions[letter].to_a.map { |x| [letter] + x }
166
- end
167
-
168
- res
169
- end
170
-
171
- #Returns the number of states.
172
- def num_states
173
- @states.size
174
- end
175
-
176
- #Returns the number of final states.
177
- def num_finals
178
- @finals.size
179
- end
180
-
181
- #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.
182
- def [](w,s)
183
- unless @states.include? s
184
- raise DFAException, "Given state '#{s}' isn't a state in this DFA."
185
- end
186
-
187
- w.scan(/./).inject(s) do |s, c|
188
- unless @alphabet.include? c
189
- raise DFAException, "Given character '#{c}' isn't in the alphabet."
65
+ #Processes given +string+ starting in state +state+.
66
+ def [](state, string)
67
+ unless @states.include?(state)
68
+ raise DFAError, "Unknown state: #{state}"
190
69
  end
191
- @transitions[c][s]
192
- end
193
- end
194
70
 
195
- #Returns true if the DFA accepts +word+.
196
- def accepts?(word)
197
- @finals.include? self[word,@initial_state]
198
- end
199
-
200
-
201
- #Returns true if there exists an isomorphism between this DFA and +other+.
202
- def isomorph_to?(other)
203
- #Test first some required conditions
204
- return false unless @alphabet == other.alphabet
205
- return false unless @states.size == other.num_states
206
- return false unless @finals.size == other.num_finals
71
+ present_state = state
72
+ string.scan(/./).each do |letter|
73
+ if @transitions[present_state].nil?
74
+ return nil
75
+ else
76
+ present_state = @transitions[present_state][letter]
77
+ end
78
+ end
207
79
 
208
- #Find an isomorphism if possible
209
- iso = @states.permutations.find do |p|
210
- isomorphism?(p, other)
80
+ present_state
211
81
  end
212
82
 
213
- iso ? true : false
214
- end
215
-
216
- #Returns true if the state is reachable, raises an Exception if the state isn't in the DFA.
217
- def reachable?(state)
218
- raise DFAException, "Unknown state: #{state}" unless @states.include? state
219
- (@reachable_states ||= reachable_states).include? state
220
- end
221
-
222
- #Returns true if given state is dead, raises an Exception if the state isn't in the DFA.
223
- def dead?(s)
224
- raise DFAException, "Unknown state: #{s}" unless @states.include? s
225
-
226
- s != @initial_state and
227
- !@finals.include?(s) and
228
- @alphabet.all? { |l| [nil,s].include? self[l,s] }
229
- end
83
+ #Processes given +string+ starting in the initial state.
84
+ def <<(string)
85
+ self[@initial_state, string]
86
+ end
230
87
 
231
- #Returns true if the dfa is minimal.
232
- def minimal?
233
- return false unless connected?
234
- get_equivalent_states.empty?
235
- end
88
+ #Checks if this DFA accepts the given +string+, i.e. halts in a final state after processing the string.
89
+ def accepts?(string)
90
+ @final_states.include?( self << string )
91
+ end
236
92
 
237
- #Returns true if the DFA is complete.
238
- def complete?
239
- @states.all? { |s| @alphabet.all? { |l| self[l,s] } }
240
- end
93
+ #Checks if given +state+ is dead, i.e. its not a final state and it hs no outgoing transitions.
94
+ def dead?(state)
95
+ raise DFAError, "Unknown state: #{state}" unless @states.include?(state)
96
+
97
+ state != @initial_state and
98
+ ! @final_states.include?(state) and
99
+ @alphabet.all? { |let| [nil,state].include? @transitions[state][let] }
100
+ end
241
101
 
242
- #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.
243
- def complete!
244
- trap = @states.find { |s| dead?(s) }
102
+ #Checks if given +state+ is reachable, i.e. it exists a +string+, such that <tt>self << string == state</tt> is true.
103
+ def reachable?(state)
104
+ raise DFAError, "Unknown state: #{state}" unless @states.include?(state)
245
105
 
246
- #No dead states present? -> create one
247
- unless trap
248
- trap = @states.sort.last.succ
249
- @states << trap
106
+ reachable_states.include? state
250
107
  end
251
108
 
252
- @states.each do |s|
253
- @alphabet.each do |l|
254
- (@transitions[l] ||= {})[s] = trap unless self[l,s]
109
+ #Checks if each state is reachable. See reachable?.
110
+ def connected?
111
+ @states.all? { |state| reachable?(state) }
112
+ end
113
+
114
+ #Checks if DFA is complete, i.e. each state accepts every alphabet letter.
115
+ def complete?
116
+ @states.all? do |state|
117
+ @alphabet.all? do |letter|
118
+ self[state,letter]
119
+ end
255
120
  end
256
121
  end
257
122
 
258
- self
259
- end
260
-
261
- #Returns +true+ if the DFA has no unreachable states.
262
- def connected?
263
- @states.all? { |s| reachable?(s) }
264
- end
265
-
266
- #Returns a DFA with no unreachable states.
267
- def connect!
268
- remove_states *@states.find_all { |s| !reachable?(s) }
269
- self
270
- end
271
-
272
- #Returns +true+ if the DFA has dead states.
273
- def dead_states?
274
- @states.any? { |s| dead?(s) }
275
- end
276
-
277
- #Removes all dead states.
278
- def remove_dead_states!
279
- remove_states *@states.find_all { |s| dead?(s) }
280
- self
281
- end
282
-
283
- =begin
284
- Minimizes the DFA. Takes as optional parameter a hash with only processed key :+rename+. The possible values are:
285
- [:+new+] States will be renamed to 's0', 's1', ...
286
- [:+min+] For each class of states the state with the smallest name is taken
287
- [:+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.
288
- =end
289
- def minimize!(opts = {})
290
- connect!
291
- remove_dead_states!
292
- unless minimal?
293
- part = state_partition
294
- merge_states *part
295
-
296
- case opts[:rename]
297
- when :new : rename_states()
298
- when :min : rename_states part.map { |cls| cls.min }
299
- else
300
- rename_states part.map { |cls| cls.join }
123
+ #Calculates the transition monoid of the DFA.
124
+ def transition_monoid
125
+ maps = [@states.dup]
126
+ elements = ['id']
127
+
128
+ length = 1
129
+ found_all_elements = false
130
+
131
+ until found_all_elements
132
+ words(length) do |word|
133
+ found_all_elements = true
134
+
135
+ map = get_map(word)
136
+ unless maps.include?(map)
137
+ maps << map
138
+ elements << word
139
+ found_all_elements = false
140
+ end
141
+ end
142
+
143
+ length += 1
301
144
  end
302
- end
303
145
 
304
- self
305
- end
146
+ monoid_description = elements.join(',')
306
147
 
307
- #Renames the state to the given array. Raises an exception if this array has the wrong size or the elements aren't unique.
308
- def rename_states(arg = nil)
309
- arg ||= (0...@states.size).map { |i| "s#{i}" }
148
+ elements[1..-1].each do |element|
149
+ monoid_description += " #{element},"
150
+ monoid_description += elements[1..-1].map do |element2|
151
+ elements[maps.index(get_map(element+element2))]
152
+ end.join(',')
153
+ end
310
154
 
311
- if arg.size != @states.size
312
- raise DFAException, "Wrong number of state names given."
155
+ RLSM::Monoid[ monoid_description ]
313
156
  end
314
157
 
315
- if arg.uniq!
316
- raise DFAException, "Given state names weren't unique."
158
+ #Checks for equality. A DFA is equal to +other+ iff it has the same alphabet, states, final states, initial state and transitions.
159
+ def ==(other)
160
+ begin
161
+ @alphabet == other.alphabet and
162
+ @initial_state == other.initial_state and
163
+ @states.sort == other.states.sort and
164
+ @final_states.sort == other.final_states.sort and
165
+ transitions.sort == other.transitions.sort
166
+ rescue
167
+ false
168
+ end
317
169
  end
318
170
 
319
- rename_transitions arg
320
-
321
- @initial_state = arg[@states.index(@initial_state)]
322
- @finals.map! { |f| arg[@states.index(f)] }
323
-
324
- @states = arg.clone
325
- end
171
+ #Checks if the DFA is minimal.
172
+ def minimal?
173
+ equivalent_state_pairs.empty?
174
+ end
326
175
 
327
- #Returns a connected copy of the DFA.
328
- def connect
329
- Marshal.load(Marshal.dump(self)).connect!
330
- end
176
+ #Alters the DFA in place to an equivalent minmal one.
177
+ def minimize!
178
+ remove_unneeded_states!
331
179
 
332
- #Returns a complete copy of the DFA.
333
- def complete
334
- Marshal.load(Marshal.dump(self)).complete!
335
- end
180
+ unless minimal?
181
+ state_classes = get_equivalent_state_classes
182
+
183
+ states = state_classes.map { |cls| cls.min }
336
184
 
337
- #Returns a minimized copy of the DFA.
338
- def minimize(opts = {})
339
- Marshal.load(Marshal.dump(self)).minimize!(opts)
340
- end
185
+ rename = {}
341
186
 
342
- #Returns +true+ if this DFA is equivalent to +other+
343
- def equivalent_to?(other)
344
- minimize.isomorph_to?(other.minimize)
345
- end
187
+ @states.each do |state|
188
+ rename[state] = state_classes.find { |cls| cls.include?(state) }.min
189
+ end
346
190
 
347
- #Returns the transition monoid of the DFA.
348
- def transition_monoid
349
- RLSM::Monoid.new get_binary_operation, :rename => true
350
- end
191
+ transitions = {}
351
192
 
352
- #Returns the transition monoid of the equivalent minimal DFA.
353
- def syntactic_monoid
354
- minimize.transition_monoid
355
- end
193
+ @transitions.each_pair do |state, transition|
194
+ start = (transitions[rename[state]] ||= {})
356
195
 
357
- #Synonym for syntactic_monoid
358
- def to_monoid
359
- syntactic_monoid
360
- end
196
+ transition.each_pair do |letter, destination|
197
+ start[letter] = rename[destination]
198
+ end
199
+ end
361
200
 
362
- #Returns self.
363
- def to_dfa
364
- self
365
- end
201
+ @initial_state = rename[@initial_state]
202
+ @states = states
203
+ @final_states.map! { |state| rename[state] }
204
+ @final_states.uniq!
205
+ @transitions = transitions
206
+ end
366
207
 
367
- #Returns a RE which represents the same language.
368
- def to_re
369
- les = []
370
- les << initialize_row_for(initial_state)
371
- (@states - [initial_state]).each do |state|
372
- les << initialize_row_for(state)
208
+ self
373
209
  end
374
210
 
375
- #Solve for the initial state
376
- les = update_les(les, simplify_les_row(les.pop)) until les.size == 1
377
-
378
- simplify_les_row(les.pop)[:final]
379
- end
380
-
381
- def inspect
382
- "<#{self.class}: #{@states.join(',')}>"
383
- end
384
-
385
- def to_s
386
- output = []
387
- output << @states.inject(['']) do |res, state|
388
- prefix = ''
389
- prefix += '*' if @finals.include? state
390
- prefix += '->' if initial_state == state
391
-
392
- res + [prefix + ' ' + state + ' ']
211
+ #Returns an equivalent minmal DFA.
212
+ def minimize
213
+ Marshal.load(Marshal.dump(self)).minimize!
393
214
  end
394
215
 
395
- @alphabet.each do |letter|
396
- column = [' ' + letter + ' ']
397
- @states.each do |state|
398
- tmp = self[letter,state]
399
- if tmp.nil?
400
- column << ' nil '
401
- else
402
- column << ' ' + tmp + ' '
216
+ #Checks if +self+ is isomorph to other.
217
+ def =~(other)
218
+ return false if other.class != self.class ||
219
+ @alphabet != other.alphabet ||
220
+ @states.size != other.states.size ||
221
+ @final_states.size != other.final_states.size ||
222
+ transitions.size != other.transitions.size
223
+
224
+ bijective_maps_to(other).any? do |bijection|
225
+ transitions.all? do |s1,s2,letter|
226
+ other.transitions.include?([bijection[s1],bijection[s2],letter])
403
227
  end
404
228
  end
405
-
406
- output << column.clone
407
229
  end
408
230
 
409
- #Align output
410
- output.map! do |col|
411
- max_length = col.map { |x| x.length }.max
412
- col.map { |x| ' '*(max_length - x.length) + x }
231
+ #Checks if +self+ is equivalent to +other+, i.e. they accepts the same language.
232
+ def equivalent_to(other)
233
+ return false if other.class != self.class
234
+
235
+ minimize =~ other.minimize
413
236
  end
414
237
 
415
- rows = (0..@states.size).map { |i| output.map { |col| col[i] } }
416
- rows.map! { |row| row.join('|') }
417
- head = rows.shift
418
- rows.unshift head.scan(/./).map { |c| c == '|' ? '+' : '-' }.join
419
- rows.unshift head
238
+ #Calculates a regular expression which represents the same languge as the DFA.
239
+ def to_regexp
240
+ les = []
241
+ les << initialize_row_for(@initial_state)
242
+ (@states - [@initial_state]).each do |state|
243
+ les << initialize_row_for(state)
244
+ end
420
245
 
421
- rows.join("\n")
422
- end
246
+ #Solve for the initial state
247
+ les = update_les(les, simplify_les_row(les.pop)) until les.size == 1
423
248
 
424
- private
425
- def initialize_row_for(state)
426
- row = { :state => state.clone }
427
- @states.each do |s|
428
- row[s] = RLSM::RE.new
249
+ simplify_les_row(les.pop)[:final]
429
250
  end
430
251
 
431
- @alphabet.each do |letter|
432
- row[self[letter, state]] += RLSM::RE.new(letter) if self[letter, state]
252
+ #Simply returns self.
253
+ def to_dfa
254
+ self
433
255
  end
434
256
 
435
- if @finals.include? state
436
- row[:final] = RLSM::RE.new(RLSM::RE::Lambda)
437
- else
438
- row[:final] = RLSM::RE.new
257
+ #Returns the transition monoid of the equivalent minimal DFA (which is in fact isomorph to the syntactic monoid of the represented language.
258
+ def to_monoid
259
+ minimize.transition_monoid
439
260
  end
440
261
 
441
- row
442
- end
262
+ private
263
+ def initialize_row_for(state)
264
+ row = { :state => state.clone, :final => RLSM::RegExp.new('') }
265
+ @states.each do |s|
266
+ row[s] = RLSM::RegExp.new ''
267
+ end
443
268
 
444
- def update_les(l,r)
445
- l.map do |row|
446
- re = row[r[:state]]
447
- @states.each do |state|
448
- if state == r[:state]
449
- row[state] = RLSM::RE.new
450
- else
451
- row[state] += re * r[state]
452
- end
269
+ @alphabet.each do |letter|
270
+ row[self[state,letter]] |= RLSM::RegExp.new(letter) if self[state,letter]
271
+ end
272
+
273
+ if @final_states.include? state
274
+ row[:final] = RLSM::RegExp.empty_word
453
275
  end
454
276
 
455
- row[:final] += re * r[:final]
456
277
  row
457
278
  end
458
- end
459
279
 
460
- def simplify_les_row(row)
461
- #Have we something like Ri = ... + xRi + ...
462
- if row[row[:state]].pattern != ''
463
- re = row[row[:state]].star
464
- @states.each do |state|
465
- if state == row[:state]
466
- row[state] = RLSM::RE.new
467
- else
468
- row[state] = re * row[state]
280
+ def update_les(les,act_row)
281
+ les.map do |row|
282
+ re = row[act_row[:state]]
283
+ @states.each do |state|
284
+ if state == act_row[:state]
285
+ row[state] = RLSM::RegExp.new ''
286
+ else
287
+ row[state] |= re + act_row[state]
288
+ end
469
289
  end
290
+
291
+ row[:final] |= re + act_row[:final]
292
+ row
470
293
  end
294
+ end
471
295
 
472
- row[:final] = re * row[:final]
473
- end
474
-
475
- row
476
- end
477
-
478
- def rename_transitions(arg)
479
- @transitions = @transitions.to_a.inject({}) do |res,pair|
480
- res[pair.first] = pair.last.map do |key,val|
481
- [arg[@states.index(key)], arg[@states.index(val)]]
482
- end.inject({}) { |r,v| r[v.first] = v.last; r }
483
- res
484
- end
485
- end
486
-
487
- def get_binary_operation
488
- maps = []
489
- elements = []
490
- l = 0
491
- loop do
492
- new = false
493
- each_word_of_length(l) do |w|
494
- map = get_map(w)
495
- unless maps.include? map
496
- maps << map
497
- elements << w
498
- new = true
296
+ def simplify_les_row(row)
297
+ #Have we something like Ri = ... + xRi + ...
298
+ if row[row[:state]].parse_tree != RLSM::RE::EmptySet[]
299
+ re = row[row[:state]].star
300
+ @states.each do |state|
301
+ if state == row[:state]
302
+ row[state] = RLSM::RegExp.new ''
303
+ else
304
+ row[state] = re + row[state]
305
+ end
499
306
  end
307
+
308
+ row[:final] = re + row[:final]
500
309
  end
501
- break unless new
502
- l += 1
503
- end
504
310
 
505
- binop = []
506
- (0...elements.size).to_a.product((0...elements.size).to_a).each do |i,j|
507
- (binop[i] ||= [])[j] = maps.index(get_map(elements[i] + elements[j]))
311
+ row
508
312
  end
509
313
 
510
- binop
511
- end
512
-
513
- def each_word_of_length(l)
514
- word = (@alphabet.first.to_s*l).scan(/./)
515
- yield word.join
516
-
517
- until word == (@alphabet.last.to_s*l).scan(/./)
518
- word = get_next_word(word)
519
- yield word.join
520
- end
521
- end
314
+ def bijective_maps_to(other)
315
+ bijective_maps = other.states.permutations.map do |perm|
316
+ Hash[*@states.zip(perm).flatten]
317
+ end
522
318
 
523
- def get_next_word(word)
524
- i = (1..word.length).find { |i| word[-i] != @alphabet.last }
525
- word[-i] = @alphabet[@alphabet.index(word[-i])+1]
526
- if i > 1
527
- (1...i).each do |j|
528
- word[-j] = @alphabet.first
319
+ bijective_maps.select do |map|
320
+ other.initial_state == map[@initial_state] &&
321
+ @final_states.all? { |fi| other.final_states.include?(map[fi]) }
529
322
  end
530
323
  end
531
324
 
532
- word
533
- end
325
+ def remove_unneeded_states!
326
+ unneeded_states, states = @states.partition do |state|
327
+ dead?(state) or not reachable?(state)
328
+ end
534
329
 
535
- def get_map(word)
536
- @states.map { |s| self[word,s] }
537
- end
330
+ transitions = self.transitions.reject do |transition|
331
+ unneeded_states.any? { |dead| transition.include?(dead) }
332
+ end
538
333
 
539
- def remove_states(*states)
540
- @reachable_states = nil
541
- #Remove the state
542
- states.each do |s|
543
- @states.delete(s)
544
- @finals.delete(s)
334
+ @states = states
335
+ @reachable_states = @states
336
+ @final_states &= states
337
+ @transitions = {}
338
+ transitions.each do |start,destination,letter|
339
+ (@transitions[start] ||= {})[letter] = destination
340
+ end
545
341
  end
342
+
343
+ def equivalent_state_pairs
344
+ @states << nil unless complete?
546
345
 
547
- #Remove the transitions
548
- @alphabet.each do |l|
549
- tr = @transitions[l].map.reject { |p| states.any? { |s| p.include? s } }
550
- @transitions[l] = tr.inject({}) { |r,x| r[x.first] = x.last; r }
551
- end
552
- end
346
+ different, indifferent = [], []
553
347
 
554
- def get_renaming_scheme(classes)
555
- classes.inject({}) do |res,cls|
556
- state = select_state_in cls
557
- cls.each do |x|
558
- res[x] = state
348
+ states[0..-2].each_with_index do |state1, i|
349
+ states[(i+1)..-1].each do |state2|
350
+ if @final_states.include?(state1) ^ @final_states.include?(state2)
351
+ different << [state1, state2]
352
+ else
353
+ indifferent << [state1, state2]
354
+ end
355
+ end
559
356
  end
560
- res
561
- end
562
- end
563
357
 
564
- def merge_states(*classes)
565
- @reachable_states = nil
566
- rename = get_renaming_scheme(classes)
358
+ begin
359
+ new_different = indifferent.select do |state1, state2|
360
+
361
+ @alphabet.any? do |letter|
362
+ test_pair = [self[state1,letter], self[state2,letter]]
363
+ different.include?(test_pair) or
364
+ different.include?(test_pair.reverse)
365
+ end
366
+ end
567
367
 
568
- @states.map! { |s| rename[s] }.uniq!
569
- @finals.map! { |s| rename[s] }.uniq!
368
+ different |= new_different
369
+ indifferent -= new_different
370
+ end until new_different.empty?
570
371
 
571
- @alphabet.each do |l|
572
- tr = @transitions[l].map { |x,y| [rename[x],rename[y]] }.uniq
573
- @transitions[l] = tr.inject({}) { |r,x| r[x.first] = x.last; r }
372
+ @states.compact!
373
+
374
+ indifferent.reject { |pair| pair.include?(nil) }
574
375
  end
575
- end
576
376
 
577
- def select_state_in(cls)
578
- if cls.include? @initial_state
579
- @initial_state
580
- else
581
- cls.min
582
- end
583
- end
377
+ def get_equivalent_state_classes
378
+ pairs = equivalent_state_pairs
379
+
380
+ classes = []
584
381
 
585
- def state_partition
586
- eq = get_equivalent_states
587
- @states.inject([]) do |res,s|
588
- res << [s]
589
- eq.find_all { |p| p.include? s }.each do |pair|
590
- res[-1] |= pair
382
+ @states.each do |state|
383
+ next if classes.any? { |cls| cls.include? state }
384
+ cls = [state]
385
+ cls |= pairs.find_all { |pair| pair.include?(state) }
386
+ classes << cls.flatten.compact.uniq
591
387
  end
592
388
 
593
- res.last.sort!
594
- res.uniq
389
+ classes
595
390
  end
596
- end
597
391
 
598
- def set_up_table_fill_algorithm
599
- (@states | (complete? ? [] : [nil])).unordered_pairs.partition do |x,y|
600
- @finals.include?(x) ^ @finals.include?(y)
392
+ def parse_initial_state(description)
393
+ unless description.count('}') == 1
394
+ raise DFAError, "None or at least two initial states."
395
+ end
396
+
397
+ @initial_state = description[/\}\s*\*?(\w+)/,1]
601
398
  end
602
- end
603
399
 
604
- def table_fill_step(dis, equi)
605
- equi.select do |x,y|
606
- @alphabet.any? do |l|
607
- dis.include? [@transitions[l][x], @transitions[l][y]] or
608
- dis.include? [@transitions[l][y], @transitions[l][x]]
400
+ def parse_final_states(description)
401
+ @final_states = []
402
+
403
+ desc = StringScanner.new(description)
404
+ loop do
405
+ break unless desc.scan(/[^*]*\*\}?/)
406
+ final_state = desc.scan(/\w+/)
407
+ @final_states << final_state unless @final_states.include?(final_state)
609
408
  end
610
409
  end
611
- end
612
410
 
613
- def get_equivalent_states
614
- distinguished, equivalent = set_up_table_fill_algorithm
411
+ def parse_states(description)
412
+ desc = description.gsub(/[*}]/,'')
413
+ desc.gsub!(/\s+/,' ')
414
+ desc.gsub!(/\s*-+(\w+,?)+-+>\s*/, ' ')
615
415
 
616
- loop do
617
- new_dis = table_fill_step distinguished, equivalent
618
- break if new_dis.empty?
619
- distinguished |= new_dis
620
- equivalent -= new_dis
416
+ @states = desc.split.uniq
621
417
  end
622
- equivalent.reject { |p| p.include? nil }
623
- end
624
418
 
625
- def reachable_states
626
- reachable = []
627
- new_states = [@initial_state]
419
+ def parse_transitions(description)
420
+ @alphabet = []
421
+ @transitions = {}
422
+
423
+ return unless description =~ /[->]/
424
+
425
+ parser = transition_parser(description)
426
+
427
+ loop do
428
+ transaction = parser.scan(/(\w+)-((\w+,?)+)->(\w+)/)
429
+ if transaction
430
+ start = parser[1]
431
+ labels = parser[2].split(',').uniq
432
+ destination = parser[4]
628
433
 
629
- until new_states.empty?
630
- reachable |= new_states
631
- new_states = []
632
- reachable.each do |s|
633
- @alphabet.map do |l|
634
- new_states << self[l,s] unless reachable.include? self[l,s]
434
+ insert_transition(start,destination,labels)
435
+ insert_alphabet_letters(labels)
436
+ elsif parser.scan(/\w+/)
437
+ #do nothing (states already parsed)
438
+ else
439
+ raise DFAError, "Parse Error, could not parse #{description}"
635
440
  end
441
+
442
+ break unless parser.scan(/ /)
636
443
  end
637
- new_states.compact!
638
- end
639
444
 
640
- reachable
641
- end
642
-
643
- def get_bijective_map_to(p, other)
644
- @states.inject({}) do |res,s|
645
- res[s] = other.states[p.index(s)]
646
- res
445
+ unless parser.eos?
446
+ raise DFAError, "Parse Error, could not parse #{description}"
447
+ end
448
+
449
+ @alphabet = @alphabet.sort
450
+ end
451
+
452
+ def transition_parser(description)
453
+ #simplifying the description for easier parsing
454
+ desc = description.gsub(/[*}]/, '')[/^\s*(.+?)\s*$/,1]
455
+ desc.gsub!(/\s+/, ' ')
456
+ desc.gsub!(/\s*-+\s*/,'-')
457
+ desc.gsub!(/>\s*/,'>')
458
+ desc.gsub!(/\s*,\s*/,',')
459
+
460
+ StringScanner.new(desc)
461
+ end
462
+
463
+ def insert_transition(start,destination,labels)
464
+ labels.each do |label|
465
+ trans = (@transitions[start] ||= {})
466
+ if trans[label].nil?
467
+ trans[label] = destination
468
+ elsif trans[label] != destination
469
+ raise DFAError, "Parse Error: Transition labels must be uniq."
470
+ end
471
+ end
647
472
  end
648
- end
649
473
 
650
- def isomorphism?(per, o)
651
- p = get_bijective_map_to(per, o)
652
- return false unless o.initial_state == p[@initial_state]
653
-
654
- unless @finals.map { |s| p[s] }.all? { |f| o.finals.include? f }
655
- return false
474
+ def insert_alphabet_letters(labels)
475
+ @alphabet |= labels
656
476
  end
657
477
 
658
- @alphabet.product(@states).all? do |c,s|
659
- p[self[c,s]] == o[c,p[s]]
478
+ def reachable_states
479
+ @reachable_states ||= calc_reachable_states
660
480
  end
661
- end
662
481
 
663
- def validate_presence_of_required_keys(args)
664
- [:alphabet,:states,:initial,:finals,:transitions].each do |key|
665
- raise DFAException, "No #{key} given!" unless args.key?(key)
482
+ def calc_reachable_states
483
+ reachable = []
484
+ new_states = [@initial_state]
485
+
486
+ until new_states.empty?
487
+ reachable |= new_states
488
+ new_states = []
489
+ reachable.each do |state|
490
+ @alphabet.each do |letter|
491
+ new_state = self[state,letter]
492
+
493
+ unless new_state.nil? or reachable.include?(new_state)
494
+ new_states << new_state
495
+ end
496
+ end
497
+ end
498
+ new_states.compact!
499
+ end
500
+
501
+ reachable
666
502
  end
667
- end
668
503
 
669
- def validate_all_states_matches(args)
670
- unless args[:states].include? args[:initial]
671
- raise DFAException, "Given initial state isn't in :states."
672
- end
673
504
 
674
- bad = args[:finals].find_all { |x| !args[:states].include?(x) }
675
- unless bad.empty?
676
- raise(DFAException,
677
- "Given final states #{bad.inspect} aren't in :states.")
505
+ def get_map(word)
506
+ @states.map { |state| self[state,word] }
678
507
  end
679
508
 
680
- bad = args[:transitions].find_all do |l,x,y|
681
- !(args[:states].include?(x) and args[:states].include?(y))
682
- end
683
- unless bad.empty?
684
- raise(DFAException,
685
- ("Given transitions #{bad.inspect}" +
686
- "have states which aren't in :states."))
687
- end
688
- end
509
+ def words(length)
510
+ word = [@alphabet.first]*length
511
+ last = [@alphabet.last]*length
689
512
 
690
- def validate_uniqueness_of_alphabet_and_states(args)
691
- [:alphabet, :states].each do |key|
692
- if args[key].uniq!
693
- raise(DFAException,
694
- "#{key} has duplicated entries: #{args[key].inspect}")
513
+ until word == last
514
+ yield word.join
515
+ word = get_next(word)
695
516
  end
517
+
518
+ yield last.join
696
519
  end
697
- end
698
520
 
699
- def validate_transition_labels_are_in_the_alphabet(args)
700
- bad = args[:transitions].find_all { |l,x,y| !args[:alphabet].include?(l) }
701
- unless bad.empty?
702
- raise(DFAException,
703
- ("Following transitions contain non alphabet characters:" +
704
- "#{bad.inspect}."))
705
- end
706
- end
707
-
708
- def validate_transition_uniquness(args)
709
- args[:alphabet].product(args[:states]).each do |c,s|
710
- tr = args[:transitions].find_all { |l,s1,s2| l == c and s1 == s }
711
- if tr.size > 1
712
- raise DFAException, ("There are multiple transitons from state " +
713
- "#{tr.first[1]} with label #{tr.first[0]}.")
521
+ def get_next(word)
522
+ i = (1..word.length).find { |i| word[-i] != @alphabet.last }
523
+ word[-i] = @alphabet[@alphabet.index(word[-i])+1]
524
+ if i > 1
525
+ (1...i).each do |j|
526
+ word[-j] = @alphabet.first
527
+ end
714
528
  end
529
+
530
+ word
715
531
  end
716
- end
717
- end
532
+ end # of class DFA
533
+ end # of module RLSM