Subversion Repositories programming

Rev

Rev 135 | Rev 137 | 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
        print ' Final     #   NFA  let1  let2'
156
        print '------------------------------'
130 ira 157
 
136 ira 158
        self.__generate_dfa_table()
159
        for i in self.dfa_table:
160
            tempstr = ''
161
            for j in i:
162
                tempstr = '%s%6s' % (tempstr, j)
131 ira 163
 
136 ira 164
            print tempstr
165
 
130 ira 166
    def __next_states(self, state, letter):
131 ira 167
        """Return the next states for the key (state, letter)"""
168
 
130 ira 169
        try:
170
            next = self.trans_func[(state, letter)]
171
        except KeyError:
131 ira 172
            # Create a new empty list for this state if it doesn't exist yet
173
            next = self.trans_func[(state, letter)] = []
130 ira 174
 
131 ira 175
        if letter == '^' and state not in next:
176
            next.append(state)
177
 
130 ira 178
        return next
131 ira 179
 
136 ira 180
    def __E(self, state, letter='^', storage=None, visited=None):
131 ira 181
        """Calculate E(state) and return it. This handles circular E()
182
        calculations."""
183
 
184
        # Work around weird mutable default argument stuff
185
        if storage is None:
186
            storage = []
187
 
188
        # Work around weird mutable default argument stuff
189
        if visited is None:
190
            visited = []
191
 
192
        # Find all of the direct next states that we can get to by
193
        # the empty string, and append anything that is not already there
136 ira 194
        for i in self.__next_states(state, letter):
131 ira 195
            if i not in storage:
196
                storage.append(i)
130 ira 197
 
131 ira 198
        # Visit everything in storage that has not been visited already
199
        for i in storage:
200
            if i not in visited:
130 ira 201
                visited.append(i)
136 ira 202
                temp = self.__E(i, letter, storage, visited)
130 ira 203
 
131 ira 204
                # Avoid duplicating things in storage
205
                for j in temp:
206
                    if j not in storage:
207
                        storage.append(j)
208
 
130 ira 209
        return storage
131 ira 210
 
136 ira 211
    def __by_letter(self, states, letter):
212
        """Returns a list of states to where you can go from a list of states,
213
        by a certain letter"""
214
 
215
        from_states = []
216
 
217
        for s in states:
218
            from_states.extend(self.__E(s))
219
 
220
        from_states = self.__unique_sort(from_states)
221
 
222
        new_states = []
223
 
224
        for s in from_states:
225
            new_states.extend(self.__next_states(s, letter))
226
 
227
        new_states = self.__unique_sort(new_states)
228
 
229
        return new_states
230
 
135 ira 231
    def __unique_sort(self, li):
232
        """Make sure everything in a list is unique, and then sort it"""
233
 
234
        newlist = []
235
 
236
        for i in li:
237
            if i not in newlist:
238
                newlist.append(i)
239
 
240
        newlist.sort()
241
        return newlist
242
 
136 ira 243
    def __is_final_state(self, state):
244
        """Check if a state is a final state."""
245
 
246
        if state in self.final_states:
247
            return True
248
 
249
        return False
250
 
251
 
252
    def __is_list_final(self, states):
253
        """Check if at least one state in the list "states" is final"""
254
 
255
        for s in states:
256
            if self.__is_final_state(s):
257
                return True
258
 
259
        return False
260
 
261
    def __get_temp_record(self, num_letters):
262
        blank = None
263
        return [blank for i in range(num_letters + 3)]
264
 
135 ira 265
    def __generate_dfa_table(self):
266
 
267
        # get the list of all letters
268
        possible_letters = []
269
 
270
        for (state, letter) in self.trans_func.keys():
271
            if letter not in possible_letters:
272
                possible_letters.append(letter)
273
 
136 ira 274
        self.possible_letters = possible_letters
135 ira 275
 
136 ira 276
        #prime the dfa table
277
        self.make_basic_record(self.__E(0))
135 ira 278
 
136 ira 279
        while self.find_unresolved_letter() != (-1, -1): #loop until we've resolved everything
280
            (record, letter_pos) = self.find_unresolved_letter()
281
            self.resolve_letter(record, letter_pos)
135 ira 282
 
136 ira 283
    def resolve_letter(self, record, letter_pos):
284
        fromstates = self.dfa_table[record][2]
285
        tostates = []
135 ira 286
 
136 ira 287
        for s in fromstates:
288
            tostates.extend(self.__next_states(s, self.possible_letters[letter_pos-3]))
135 ira 289
 
136 ira 290
        self.dfa_table[record][letter_pos] = self.make_basic_record(tostates)
135 ira 291
 
136 ira 292
    def find_unresolved_letter(self): # WORKING
293
        """Returns an index pair of an unresolved letter that exists in the dfa_table.
294
        If there are no more letters, return (-1, -1)"""
135 ira 295
 
136 ira 296
        for i in range(len(self.dfa_table)):
297
            for j in range(len(self.possible_letters)):
298
                if self.dfa_table[i][j+3] == None:
299
                    return (i, j+3)
135 ira 300
 
136 ira 301
        return (-1, -1)
135 ira 302
 
136 ira 303
    def make_basic_record(self, from_states):
135 ira 304
 
136 ira 305
        if self.dfa_state_exists(from_states) == -1: #doesn't exist
306
            temp_record = self.__get_temp_record(len(self.possible_letters))
307
            recordnum = len(self.dfa_table)
135 ira 308
 
136 ira 309
            temp_record[1] = recordnum
310
            temp_record[2] = self.__unique_sort(from_states)
311
            temp_record[0] = self.__is_list_final(from_states)
135 ira 312
 
136 ira 313
            self.dfa_table.append(temp_record)
135 ira 314
 
136 ira 315
        # always return a re-call of the function. This will not fail, since a new
316
        # record is added above if a record does not already exist.
317
        return self.dfa_state_exists(from_states)
135 ira 318
 
319
 
136 ira 320
    def dfa_state_exists(self, from_states):
321
        """Find out if this state already exists in the dfa table.
322
        If it does exist, return it's dfa state name.
323
        If it does not exist, return -1"""
130 ira 324
 
136 ira 325
        sorted_states = self.__unique_sort(from_states)
326
 
327
        for i in range(len(self.dfa_table)):
328
            if self.dfa_table[i][2] == sorted_states:
329
                return self.dfa_table[i][1]
330
 
331
        return -1
332
 
130 ira 333
if __name__ == '__main__':
334
    automaton = nfa()
335
    automaton.main_menu()
336