Subversion Repositories programming

Rev

Rev 137 | Rev 140 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
130 ira 1
#!/usr/bin/env python
2
# Copyright: Ira W. Snyder
3
# Start Date: 2005-10-08
131 ira 4
# End Date:
130 ira 5
# License: Public Domain
6
#
7
# Changelog Follows:
131 ira 8
# - 2005-10-16
9
# - Implemented the nfa class. It's mostly complete at this point.
10
# - E() function works for circular loops now.
11
# - Made the nfa.__next_states() function always return a valid reference
12
#   to a list in the dictionary. This means you should NEVER use
13
#   self.trans_func[(state, letter)] in code anywhere now :)
14
# - Final states are now checked for validity.
130 ira 15
#
16
 
17
################################################################################
18
# IMPORTANT NOTES
19
################################################################################
20
################################################################################
21
 
22
import sys, string
23
 
24
class nfa:
25
    """Implements a Non-Deterministic Finite Automaton"""
26
 
27
    def __init__(self):
138 ira 28
        self.__clear()
29
 
30
    def __clear(self):
130 ira 31
        self.trans_func = {}
32
        self.num_states = 0
33
        self.final_states = []
34
        self.initial_state = 0
136 ira 35
        self.possible_letters = []
36
        self.dfa_table = []
131 ira 37
 
130 ira 38
    def __state_in_range(self, state):
39
        """Check if a given state is in the acceptable range"""
40
 
41
        try:
42
            temp = int(state)
43
        except (TypeError, ValueError):
44
            return False
45
 
46
        if temp >= 0 and temp < self.num_states:
47
            return True
48
 
49
        return False
131 ira 50
 
130 ira 51
    def __input_num_states(self):
52
        """Get the number of states this automaton will have"""
53
 
54
        done = False
55
        while not done:
56
            num_states = raw_input('How many states in this automaton: ')
138 ira 57
            print
130 ira 58
 
59
            try:
60
                self.num_states = int(num_states)
61
                done = True
62
            except (TypeError, ValueError):
63
                print 'Bad input, not an integer, try again'
64
                print
65
 
66
    def __input_final_states(self):
67
        """Get final states for this automaton"""
68
 
69
        print 'Enter final states on one line, seperated by spaces'
131 ira 70
 
130 ira 71
        done = False
72
        while not done:
73
            final_states = raw_input('Final states: ')
138 ira 74
            print
131 ira 75
 
130 ira 76
            bad_data = False
77
            for i in final_states.split():
78
                if not self.__state_in_range(i):
79
                    print 'State %s out of range. All input discarded.' % (i, )
80
                    print 'Try again please'
81
                    print
82
                    bad_data = True
83
                    break
84
 
85
            # if we left the for loop with bad data
86
            if bad_data:
87
                continue
88
 
89
            # all data is good
90
            for i in final_states.split():
131 ira 91
                if self.__state_in_range(i):
92
                    self.final_states.append(int(i))
130 ira 93
 
94
            done = True
95
 
96
    def __input_all_edges(self):
97
        """Read all of the edges for this automaton"""
98
 
99
        print 'Edges take the form "from HERE, by LETTER, to THERE"'
100
        print 'Enter something like: "1 a 1" to represent the following'
101
        print 'from q1, by a, go to q1'
102
        print
103
        print 'Conditions:'
104
        print '1. Blank line to end'
105
        print '2. ^ is the empty string'
138 ira 106
        print
130 ira 107
 
108
        done = False
109
        while not done:
110
            edge = raw_input('Edge: ')
111
 
112
            if len(edge) == 0:
113
                done = True
114
                continue
131 ira 115
 
130 ira 116
            if len(edge.split()) != 3:
117
                print 'Bad edge entered'
118
                print
119
                continue
131 ira 120
 
130 ira 121
            (cur_st, letter, next_st) = edge.split()
122
 
131 ira 123
            # Make sure the states entered are in range
130 ira 124
            if self.__state_in_range(cur_st) and self.__state_in_range(next_st):
125
                new_state = False
131 ira 126
                cur_st = int(cur_st)
127
                next_st = int(next_st)
130 ira 128
 
131 ira 129
                # Add the state to the list if it's not there already
130
                if next_st not in self.__next_states(cur_st, letter):
131
                    self.__next_states(cur_st, letter).append(next_st)
130 ira 132
 
133
            else:
134
                print 'Invalid current or next state entered'
135
                print
136
                continue
131 ira 137
 
130 ira 138
    def __input_automaton(self):
139
        """Read this entire automaton's input"""
140
 
141
        self.__input_num_states()
142
        self.__input_final_states()
143
        self.__input_all_edges()
144
 
136 ira 145
        # Make sure to visit all '^' states (to make printing easier, later)
146
        for i in range(self.num_states):
147
            self.__E(i)
148
 
130 ira 149
    def main_menu(self):
138 ira 150
        done = False
151
        automaton_entered = False
131 ira 152
 
138 ira 153
        while not done:
154
            print 'Menu:'
155
            print '========================================'
156
            print '1. Enter an NFA'
157
            print '2. Output corresponding DFA'
158
            print '3. Quit'
159
            print
160
            s = raw_input('Choice >>> ')
161
            print
130 ira 162
 
138 ira 163
            if s == '1':
164
                self.__clear()
165
                self.__input_automaton()
166
                automaton_entered = True
167
                print
168
            elif s == '2':
169
                if automaton_entered:
170
                    self.__output_dfa()
171
                else:
172
                    print 'Enter a NFA first'
173
                print
174
            elif s == '3':
175
                done = True
176
            else:
177
                print 'Bad Selection'
178
                print
179
 
180
    def __output_dfa(self):
131 ira 181
        print
136 ira 182
        print 'Initial State: %s' % (0, )
183
        print
184
        self.__generate_dfa_table()
137 ira 185
        self.__print_dfa_table()
138 ira 186
        print
131 ira 187
 
137 ira 188
    def __print_dfa_table(self):
138 ira 189
        #header1 = '%-8s%-8s%-20s' % ('Final', 'Number', 'NFA Equiv')
190
        header1 = '%-8s%-8s' % ('Final', 'State #')
137 ira 191
        header2 = ''
136 ira 192
 
137 ira 193
        for l in self.possible_letters:
194
            header2 = '%s%-4s' % (header2, l)
195
 
196
        heading = '%s %s' % (header1, header2)
197
        hdr_line = ''
138 ira 198
 
137 ira 199
        for i in range(len(heading)):
200
            hdr_line = '%s-' % (hdr_line, )
201
 
202
        print heading
203
        print hdr_line
204
 
205
        for rec in self.dfa_table:
138 ira 206
            #line1 = '%-8s%-8s%-20s' % (rec[0], rec[1], rec[2])
207
            line1 = '%-8s%-8s' % (rec[0], rec[1])
137 ira 208
            line2 = ''
138 ira 209
 
137 ira 210
            for l in range(len(self.possible_letters)):
211
                line2 = '%s%-4s' % (line2, rec[l+3])
212
 
213
            print '%s %s' % (line1, line2)
214
 
130 ira 215
    def __next_states(self, state, letter):
131 ira 216
        """Return the next states for the key (state, letter)"""
217
 
130 ira 218
        try:
219
            next = self.trans_func[(state, letter)]
220
        except KeyError:
131 ira 221
            # Create a new empty list for this state if it doesn't exist yet
222
            next = self.trans_func[(state, letter)] = []
130 ira 223
 
131 ira 224
        if letter == '^' and state not in next:
225
            next.append(state)
226
 
130 ira 227
        return next
131 ira 228
 
136 ira 229
    def __E(self, state, letter='^', storage=None, visited=None):
131 ira 230
        """Calculate E(state) and return it. This handles circular E()
231
        calculations."""
232
 
233
        # Work around weird mutable default argument stuff
234
        if storage is None:
235
            storage = []
236
 
237
        # Work around weird mutable default argument stuff
238
        if visited is None:
239
            visited = []
240
 
241
        # Find all of the direct next states that we can get to by
242
        # the empty string, and append anything that is not already there
136 ira 243
        for i in self.__next_states(state, letter):
131 ira 244
            if i not in storage:
245
                storage.append(i)
130 ira 246
 
131 ira 247
        # Visit everything in storage that has not been visited already
248
        for i in storage:
249
            if i not in visited:
130 ira 250
                visited.append(i)
136 ira 251
                temp = self.__E(i, letter, storage, visited)
130 ira 252
 
131 ira 253
                # Avoid duplicating things in storage
254
                for j in temp:
255
                    if j not in storage:
256
                        storage.append(j)
257
 
130 ira 258
        return storage
131 ira 259
 
136 ira 260
    def __by_letter(self, states, letter):
261
        """Returns a list of states to where you can go from a list of states,
262
        by a certain letter"""
263
 
264
        from_states = []
265
 
266
        for s in states:
267
            from_states.extend(self.__E(s))
268
 
269
        from_states = self.__unique_sort(from_states)
270
 
271
        new_states = []
272
 
273
        for s in from_states:
274
            new_states.extend(self.__next_states(s, letter))
275
 
276
        new_states = self.__unique_sort(new_states)
277
 
278
        return new_states
279
 
135 ira 280
    def __unique_sort(self, li):
281
        """Make sure everything in a list is unique, and then sort it"""
282
 
283
        newlist = []
284
 
285
        for i in li:
286
            if i not in newlist:
287
                newlist.append(i)
288
 
289
        newlist.sort()
290
        return newlist
291
 
136 ira 292
    def __is_final_state(self, state):
293
        """Check if a state is a final state."""
294
 
295
        if state in self.final_states:
296
            return True
297
 
298
        return False
299
 
300
 
301
    def __is_list_final(self, states):
302
        """Check if at least one state in the list "states" is final"""
303
 
304
        for s in states:
305
            if self.__is_final_state(s):
306
                return True
307
 
308
        return False
309
 
310
    def __get_temp_record(self, num_letters):
311
        blank = None
312
        return [blank for i in range(num_letters + 3)]
313
 
138 ira 314
    def __get_possible_letters(self):
315
 
135 ira 316
        # get the list of all letters
317
        possible_letters = []
318
 
319
        for (state, letter) in self.trans_func.keys():
137 ira 320
            if letter not in possible_letters and letter != '^':
135 ira 321
                possible_letters.append(letter)
322
 
138 ira 323
        possible_letters = self.__unique_sort(possible_letters)
136 ira 324
        self.possible_letters = possible_letters
138 ira 325
 
326
    def __generate_dfa_table(self):
135 ira 327
 
138 ira 328
        # get all the possible letters
329
        self.__get_possible_letters()
330
 
331
        # prime the dfa table
136 ira 332
        self.make_basic_record(self.__E(0))
135 ira 333
 
136 ira 334
        while self.find_unresolved_letter() != (-1, -1): #loop until we've resolved everything
335
            (record, letter_pos) = self.find_unresolved_letter()
336
            self.resolve_letter(record, letter_pos)
135 ira 337
 
136 ira 338
    def resolve_letter(self, record, letter_pos):
339
        fromstates = self.dfa_table[record][2]
340
        tostates = []
135 ira 341
 
136 ira 342
        for s in fromstates:
343
            tostates.extend(self.__next_states(s, self.possible_letters[letter_pos-3]))
135 ira 344
 
136 ira 345
        self.dfa_table[record][letter_pos] = self.make_basic_record(tostates)
135 ira 346
 
136 ira 347
    def find_unresolved_letter(self): # WORKING
348
        """Returns an index pair of an unresolved letter that exists in the dfa_table.
349
        If there are no more letters, return (-1, -1)"""
135 ira 350
 
136 ira 351
        for i in range(len(self.dfa_table)):
352
            for j in range(len(self.possible_letters)):
353
                if self.dfa_table[i][j+3] == None:
354
                    return (i, j+3)
135 ira 355
 
136 ira 356
        return (-1, -1)
135 ira 357
 
136 ira 358
    def make_basic_record(self, from_states):
135 ira 359
 
136 ira 360
        if self.dfa_state_exists(from_states) == -1: #doesn't exist
361
            temp_record = self.__get_temp_record(len(self.possible_letters))
362
            recordnum = len(self.dfa_table)
135 ira 363
 
136 ira 364
            temp_record[1] = recordnum
365
            temp_record[2] = self.__unique_sort(from_states)
366
            temp_record[0] = self.__is_list_final(from_states)
135 ira 367
 
136 ira 368
            self.dfa_table.append(temp_record)
135 ira 369
 
136 ira 370
        # always return a re-call of the function. This will not fail, since a new
371
        # record is added above if a record does not already exist.
372
        return self.dfa_state_exists(from_states)
135 ira 373
 
374
 
136 ira 375
    def dfa_state_exists(self, from_states):
376
        """Find out if this state already exists in the dfa table.
377
        If it does exist, return it's dfa state name.
378
        If it does not exist, return -1"""
130 ira 379
 
136 ira 380
        sorted_states = self.__unique_sort(from_states)
381
 
382
        for i in range(len(self.dfa_table)):
383
            if self.dfa_table[i][2] == sorted_states:
384
                return self.dfa_table[i][1]
385
 
386
        return -1
387
 
130 ira 388
if __name__ == '__main__':
389
    automaton = nfa()
390
    automaton.main_menu()
391