Subversion Repositories programming

Rev

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