rlsm 0.2.4 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +0 -0
- data/Manifest.txt +3 -28
- data/README.txt +0 -0
- data/Rakefile +0 -0
- data/bin/smon +1 -3
- data/lib/data/monoids.db +0 -0
- data/lib/dfa.rb +656 -0
- data/lib/monoid.rb +778 -0
- data/lib/re.rb +492 -0
- data/lib/rlsm.rb +57 -36
- metadata +5 -30
- data/lib/rlsm/dfa.rb +0 -705
- data/lib/rlsm/exceptions.rb +0 -39
- data/lib/rlsm/mgen.rb +0 -138
- data/lib/rlsm/monkey_patching.rb +0 -126
- data/lib/rlsm/monoid.rb +0 -552
- data/lib/rlsm/monoid_db.rb +0 -123
- data/lib/rlsm/regexp.rb +0 -229
- data/lib/rlsm/regexp_nodes/concat.rb +0 -112
- data/lib/rlsm/regexp_nodes/primexp.rb +0 -49
- data/lib/rlsm/regexp_nodes/renodes.rb +0 -95
- data/lib/rlsm/regexp_nodes/star.rb +0 -50
- data/lib/rlsm/regexp_nodes/union.rb +0 -85
- data/lib/smon/commands/db_find.rb +0 -37
- data/lib/smon/commands/db_stat.rb +0 -20
- data/lib/smon/commands/exit.rb +0 -9
- data/lib/smon/commands/help.rb +0 -31
- data/lib/smon/commands/intro.rb +0 -32
- data/lib/smon/commands/monoid.rb +0 -27
- data/lib/smon/commands/quit.rb +0 -10
- data/lib/smon/commands/regexp.rb +0 -20
- data/lib/smon/commands/reload.rb +0 -22
- data/lib/smon/commands/show.rb +0 -21
- data/lib/smon/presenter.rb +0 -18
- data/lib/smon/presenter/txt_presenter.rb +0 -157
- data/lib/smon/smon.rb +0 -79
- data/test/dfa_spec.rb +0 -99
- data/test/monoid_spec.rb +0 -270
- data/test/regexp_spec.rb +0 -25
data/History.txt
CHANGED
File without changes
|
data/Manifest.txt
CHANGED
@@ -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
data/lib/data/monoids.db
CHANGED
File without changes
|
data/lib/dfa.rb
ADDED
@@ -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
|