def _initLogging(self): self.log = AutomatLogger().log
class NichtDeterministischerAutomat(automatenausgabe.OAsciiAutomat, automatenausgabe.OLaTeXAutomat, automatenausgabe.ODotAutomat, automatenausgabe.OPlaintextAutomat): def _toList(self, what): """ >>> mini = NichtDeterministischerAutomat('s0', 's0', 's0', '0 1', { 's0' : {'0' : 's0'}}) >>> mini._toList('') [''] >>> mini._toList('a b c d') ['a', 'b', 'c', 'd'] >>> mini._toList(['a', 'b', 'c', 'd']) ['a', 'b', 'c', 'd'] """ if isinstance(what, basestring): return what.split(' ') elif isinstance(what, list): return what elif isinstance(what, tuple): return list(what) elif isinstance(what, frozenset): return list(what) else: raise ValueError("Cannot convert '%s' to list()" % str(what)) def _toFrozenSet(self, what): """ >>> mini = NichtDeterministischerAutomat('s0', 's0', 's0', '0 1', { 's0' : {'0' : 's0'}}) >>> mini._toFrozenSet('a c b') frozenset(['a', 'c', 'b']) >>> mini._toFrozenSet('z1') frozenset(['z1']) >>> mini._toFrozenSet(set(['a', 'c', 'b'])) frozenset(['a', 'c', 'b']) """ if isinstance(what, set): return frozenset(what) return frozenset(self._toList(what)) def _fzString(self, what): """ String-Representation einer Menge (frozenset). >>> mini = NichtDeterministischerAutomat('s0', 's0', 's0', '0 1', { 's0' : {'0' : 's0'}}) >>> mini._fzString(frozenset([])) '{}' >>> mini._fzString(frozenset(['a', 'c', 'b'])) '{a,b,c}' """ return '{%s}' % ','.join(sorted(what)) def _int2bin(self, value, fill=0): """ >>> A = NichtDeterministischerAutomat('z0 z1', 'z0', 'z0', '0 1', {'z0' : {'0' : 'z0', '1' : 'z1'}, 'z1' : {'0' : 'z0', '1' : 'z1'}}) >>> b2 = A._int2bin(2) >>> b2 '10' >>> A.check(b2) True >>> b1 = A._int2bin(1) >>> b1 '1' >>> A.check(b1) False """ result = list() while value: result.append(str(value & 1)) value >>= 1 result.reverse() return ''.join(result).zfill(fill) def _fixDeltaMapping(self, delta): """ Sorgt dafuer, dass das zurueckgegebene dictionary die folgende Struktur hat: { Zustand : { <Zeichen-Set> : <Zustand-Set> } } >>> delta = { 's0' : {'0' : 's0'} } >>> delta {'s0': {'0': 's0'}} >>> mini = NichtDeterministischerAutomat('s0', 's0', 's0', '0 1', delta) >>> deltaNeu = mini._fixDeltaMapping(delta) >>> deltaNeu {'s0': {frozenset(['0']): frozenset(['s0'])}} >>> oDelta = { 'a' : {'a b c' : 'd e f'} } >>> nDelta = mini._fixDeltaMapping(oDelta) >>> nDelta {'a': {frozenset(['a', 'c', 'b']): frozenset(['e', 'd', 'f'])}} """ deltaNeu = dict() for zustand in delta.keys(): deltaNeu[zustand] = dict() for zeichen in delta[zustand]: ziel = delta[zustand][zeichen] sZeichen = self._toFrozenSet(zeichen) sZiel = self._toFrozenSet(ziel) deltaNeu[zustand][sZeichen] = sZiel loeschBar = list() zielDict = dict() for zeichenMenge in deltaNeu[zustand]: zielMenge = deltaNeu[zustand][zeichenMenge] # self.log.debug("** Zustand '%s' : %s => %s" % (zustand, zeichenMenge, zielMenge)) if zielMenge not in zielDict: zielDict[zielMenge] = frozenset() # self.log.debug("== Old: %s, New: %s" % (zielDict[zielMenge], zeichenMenge)) zielDict[zielMenge] = zielDict[zielMenge].union(zeichenMenge) # self.log.debug(">> Now: %s" % (zielDict[zielMenge])) loeschBar.append(zeichenMenge) # self.log.error("XX " + self.dump(deltaNeu)) for zeichenMenge in loeschBar: # self.log.debug("-- loesche Uebergang fuer '%s': %s" % (zustand, zeichenMenge)) del (deltaNeu[zustand][zeichenMenge]) # self.log.error("YY " + self.dump(deltaNeu)) zustandDict = dict() for ziel in zielDict: zeichen = zielDict[ziel] zustandDict[zeichen] = ziel self.log.debug(">> Zustand '%s' : %s => %s" % (zustand, zeichen, ziel)) deltaNeu[zustand][zeichen] = ziel # self.log.error("ZZ " + self.dump(deltaNeu)) # self.log.debug(deltaNeu) return deltaNeu def _initLogging(self): self.log = AutomatLogger().log def __init__(self, S, s0, F, Sigma, delta, name="EinNDA", beschreibung='', testWords=None, verifyWords=None, verifyRegExp=None): """ >>> mini = NichtDeterministischerAutomat('s0 s1 s2 s3', 's0', 's3', '0 1', {'s0' : {'0' : ['s0', 's1'], '1' : 's0'}, 's1' : {'0' : 's2', '1' : 's2'}, 's2' : { '0' : 's3', '1' : 's3'}}) >>> mini._delta('s0', '0') frozenset(['s1', 's0']) >>> mini._delta('s0', 'b') Traceback (most recent call last): ... NotInSigmaException: 'b' ist nicht Teil der Menge der Eingabezeichen [0,1] >>> mini._delta('zX', '0') Traceback (most recent call last): ... NoSuchStateException: 'zX' ist nicht Teil der Menge der moeglichen Zustaende [s0,s1,s2,s3] >>> mini = NichtDeterministischerAutomat('s0 s1 s2 s3', 'zY', 's3', '0 1', {'s0' : {'0' : ['s0', 's1'], '1' : 's0'}, 's1' : {'0' : 's2', '1' : 's2'}, 's2' : { '0' : 's3', '1' : 's3'}}) Traceback (most recent call last): ... NoSuchStateException: *STARTZUSTAND* frozenset(['zY']) ist nicht Teil der Menge der moeglichen Zustaende [s0,s1,s2,s3] >>> mini = NichtDeterministischerAutomat('s0 s1 s2 s3', 's0', 'zZ', '0 1', {'s0' : {'0' : ['s0', 's1'], '1' : 's0'}, 's1' : {'0' : 's2', '1' : 's2'}, 's2' : { '0' : 's3', '1' : 's3'}}) Traceback (most recent call last): ... NoSuchStateException: *ENDZUSTAENDE* frozenset(['zZ']) ist nicht Teil der Menge der moeglichen Zustaende [s0,s1,s2,s3] >>> mini = NichtDeterministischerAutomat('s0 s1 s2 s3', 's0', 's3', '0 1', {'NotInAlphabet' : {'0' : ['s0', 's1'], '1' : 's0'}, 's1' : {'0' : 's2', '1' : 's2'}, 's2' : { '0' : 's3', '1' : 's3'}}) Traceback (most recent call last): ... NoSuchStateException: *UEBERFUEHRUNGSFUNKTION* frozenset(['NotInAlphabet']) ist nicht Teil der Menge der moeglichen Zustaende [s0,s1,s2,s3] @param S: Endliche Menge der moeglichen Zustaende @param s0: Anfangszustaende @param F: Menge der Endzustaende @param Sigma: Endliche Menge der Eingabezeichen @param delta: Zustands-Ueberfuehrungstabelle/dict() @param name: Bezeichner fuer den Automaten @param beschreibung: Beschreibung fuer den Automaten """ self._initLogging() # Umwandeln von Listen und Stringinhalte (whitespace-getrennt) in frozenset-Mengen S = self._toFrozenSet(S) F = self._toFrozenSet(F) Sigma = self._toFrozenSet(Sigma) s0 = self._toFrozenSet(s0) # Ueberpruefen I - S, s0, F, Sigma sowie delta duerfen nicht leer sein. if len(S) == 0: raise ValueError('Die "endliche Menge der möglichen Zustände" S des Automaten ist leer') if len(s0) == 0: raise ValueError('Die "Menge der Anfangszustände" des Automaten ist leer') if len(F) == 0: raise ValueError('Die "Menge der Endzustände" F ist leer') if len(Sigma) == 0: raise ValueError('Die "endliche Menge der Eingabezeichen, Alphabet" Σ (Sigma) ist leer') if len(delta) == 0: raise ValueError('Die "(determinierte) Zustands-Überführungsfunktion" δ (delta) ist leer') # Ueberpruefen II - s0 und F muessen Untermengen von S sein if not s0.issubset(S): raise NoSuchStateException(s0, S, hint="Startzustand") if not F.issubset(S): raise NoSuchStateException(F, S, hint="Endzustaende") self.S = S self.s0 = s0 self.F = F self.Sigma = Sigma self.delta = self._fixDeltaMapping(delta) self.ableitungsPfad = list() # Ueberpruefen III - das delta Ueberfuehrungsregelwerk soll keine Uebergaenge fuer Zustaende # definieren, die nicht eine Untermenge von S sind fzDeltaKeys = frozenset(self.delta.keys()) if not fzDeltaKeys.issubset(self.S): raise NoSuchStateException(fzDeltaKeys.difference(self.S), self.S, "Ueberfuehrungsfunktion") # Ueberpruefen IV - das delta Ueberfuehrungsregelwerk soll keine Uebergaenge fuer Zustaende # definieren, die Zeichen benutzen, die nicht in Sigma sind for zustand in fzDeltaKeys: zZeichen = self.delta[zustand].keys() zeichenSet = set() for item in zZeichen: addme = list(item)[0] zeichenSet.add(addme) self.log.debug('%s : %s, in Sigma: %s' % (zustand, zeichenSet, zeichenSet.issubset(self.Sigma))) if not zeichenSet.issubset(self.Sigma): raise NotInSigmaException(zeichenSet.difference(self.Sigma), self.Sigma) # Ein paar meta Daten .. self.name = name self.abbildungen = 0 self.ZustandIndex = dict() if testWords: self.testWords = self._toList(testWords) else: self.testWords = None self.log.warning("[%s] No testwords provided." % self.name) self.verifyWords = verifyWords self.verifyRegExp = verifyRegExp self.beschreibung = beschreibung #: "Roh"-Daten des Ableitungspfades self.raw_ableitung = list() self.type = 'finite' if self.testWords == None: self.log.debug("Adding Test Words") self.testWords = self.testWorteGenerator() # Automat zuruecksetzen (aktuellen Zustand auf s0 setzen) self.reset() def _ableitungAppend(self, items): self.raw_ableitung.append(copy.deepcopy(items)) # self.log.error("// + _ableitungAppend(%s)" % self.raw_ableitung[-1]) def _ableitungReset(self): self.raw_ableitung = list() def _ableitungToString(self): return str(self.raw_ableitung) def _ableitungsPfad__str__(self): if len(self.ableitungsPfad) == 0: return '' items = list() for item in self.ableitungsPfad: items.append('{%s}' % ','.join(sorted(item))) return " Ableitung:\n %s" % ' -> '.join(items) def __str__(self): s = "%seterministischer Automat '%s'" % ((self.istDEA() and 'D' or 'Nichtd'), self.name) if EpsilonAutomat.EPSILON in self.Sigma: s += " (ε-Übergänge möglich)" s += "\n" if self.beschreibung: s += " %s\n" % self.beschreibung s += " Anfangszustand : %s\n" % self._fzString(self.s0) s += " Endliche Menge der möglichen Zustände S : %s\n" % self._fzString(self.S) s += " Menge der Endzustände F : %s\n" % self._fzString(self.F) s += " Endliche Menge der Eingabezeichen Σ : %s\n" % self._fzString(self.Sigma) if '_getAsciiArtDeltaTable' in dir(self): s += self._getAsciiArtDeltaTable() s += " (%se Überführungsfunktion)\n" % (self.istDeltaVollstaendig() and 'vollständig' or 'partiell') return s def dump(self, delta=None): if not delta: delta = self.delta l = list() for zustand in sorted(delta): l.append("%s" % (zustand)) for zeichenMenge in sorted(delta[zustand]): zielMenge = delta[zustand][zeichenMenge] l.append(" %-10s : %s" % (repr(zeichenMenge), repr(zielMenge))) return "\n".join(l) def reset(self): """ Setzt den Automaten zurueck """ self.Zustand = self.s0 self._ableitungReset() self.ableitungsPfad = list() def istDEA(self): """ Ein DEA zeichnet sich dadurch aus, dass * s0 ein einzelner Anfangszustand und * jedem Paar(s, a) aus [S kreuz Sigma] ein einzelner Funktionswert (Zustand) zugeordnet ist. >>> mini = NichtDeterministischerAutomat('s0 s1 s2 s3', 's0', 's3', '0 1', {'s0' : {'0' : 's0 s1', '1' : 's0'}, 's1' : {'0' : 's2', '1' : 's2'}, 's2' : { '0' : 's3', '1' : 's3'}}) >>> mini.istDEA() False >>> m2 = NichtDeterministischerAutomat('s0 s1 s2 s3', 's0', 's3', '0 1', {'s0' : {'0' : 's0', '1' : 's0'}, 's1' : {'0' : 's2', '1' : 's2'}, 's2' : { '0' : 's3', '1' : 's3'}}) >>> m2.istDEA() True """ if (len(self.s0) != 1): return False for zustand in self.delta: for zeichen in self.delta[zustand]: if len(self.delta[zustand][zeichen]) != 1: return False return True def istDeltaVollstaendig(self): """ >>> mini = NichtDeterministischerAutomat('s0 s1 s2 s3', 's0', 's3', '0 1', {'s0' : {'0' : 's0 s1', '1' : 's0'}, 's1' : {'0' : 's2', '1' : 's2'}, 's2' : { '0' : 's3', '1' : 's3'}}) >>> mini.istDeltaVollstaendig() False >>> m2 = NichtDeterministischerAutomat('s0 s1 s2 s3', 's0', 's3', '0 1', {'s0' : {'0' : 's0 s1', '1' : 's0'}, 's1' : {'0' : 's2', '1' : 's2'}, 's2' : { '0' : 's3', '1' : 's3'}, 's3' : {}}) >>> m2.istDeltaVollstaendig() True """ return len(self.F.difference(self.delta.keys())) == 0 def testWorteGenerator(self, length=3, Sigma=None): """ Generiert Testworte der gewuenschten Laenge bestehend aus dem Alphabet des Automaten. Optional kann Sigma angegeben werden >>> mini = NichtDeterministischerAutomat('s0 s1 s2 s3', 's0', 's3', '0 1', {'s0' : {'0' : 's0 s1', '1' : 's0'}, 's1' : {'0' : 's2', '1' : 's2'}, 's2' : { '0' : 's3', '1' : 's3'}}) >>> mini.testWorteGenerator(length=1) ['1', '0', '11', '01', '10', '00'] >>> mini.testWorteGenerator(Sigma=['a', 'b'], length=1) ['a', 'b', 'aa', 'ba', 'ab', 'bb'] @param length: Maximal-Laenge der generierten Worte @param Sigma: (optional) Alternativ-Alphabet @return: Liste mit Testworten """ if Sigma == None: Sigma = self.Sigma worte = list(Sigma) SigmaTmp = Sigma for i in xrange(length): SigmaTmp = [a + b for b in Sigma for a in SigmaTmp] worte += SigmaTmp return worte def _delta__str__(self, Zustand, Zeichen): """ Delta Funktion, fuer Aufrufe innerhalb von __str__() Aufrufen verwendet werden kann. """ return self._delta(Zustand, Zeichen) def _delta(self, Zustand, Zeichen): """ Zustands-Ueberfuehrungsfunktion >>> mini = NichtDeterministischerAutomat('s0 s1 s2 s3', 's0', 's3', '0 1', {'s0' : {'0' : 's0 s1', '1' : 's0'}, 's1' : {'0' : 's2', '1' : 's2'}, 's2' : { '0' : 's3', '1' : 's3'}}) >>> mini.S frozenset(['s3', 's2', 's1', 's0']) >>> mini.F frozenset(['s3']) >>> mini.Sigma frozenset(['1', '0']) >>> mini._delta(['s0', 's1'], 1) frozenset(['s2', 's0']) >>> mini._delta(frozenset(['s0', 's1']), 1) frozenset(['s2', 's0']) >>> mini._delta(['s0', 's1'], 1) frozenset(['s2', 's0']) >>> mini._delta(['s0', 's1', 's3'], 1) Traceback (most recent call last): ... NoRuleForStateException: 's3' hat keine definierten Regeln. [s0,s1,s2] >>> mini._delta('s0', '1') frozenset(['s0']) @param Zustand: Quell-Zustand (falls kein list()-Objekt: wird mittels str() umgewandelt) @param Zeichen: einzulesendes Zeichen (wird mittels str() umgewandelt) @return: Menge der erreichten Zustaende oder leere Menge """ # Sicherstellen, dass Zeichen ein String ist Zeichen = str(Zeichen) # self.log.debug("_delta(%s, %s)" % (Zustand, Zeichen)) if Zustand == frozenset([]): self.log.error("[%s] Zustand '%s' Ausgangszustand ist leere Menge!" % (self.name, repr(Zustand))) return frozenset([]) # Pruefen, ob das zu lesende Zeichen ueberhaupt Teil der Menge der Eingabezeichen ist if Zeichen not in self.Sigma: self.log.debug(" '%s' nicht Teil des Alphabets (%s)" % (Zeichen, self._fzString(self.Sigma))) raise NotInSigmaException(Zeichen, self.Sigma) # Sonderbehandlung: Zustand kann auch eine Liste von Zustaenden sein if isinstance(Zustand, list): ziele = frozenset() for item in Zustand: ziele = ziele.union(self._delta(item, Zeichen)) return ziele elif isinstance(Zustand, basestring): # Ansonsten: Zustand in frozenset verwandeln Zustand = frozenset([str(Zustand)]) elif isinstance(Zustand, frozenset): if len(Zustand) > 1: # self.log.warning("!! Zustand: schon frozenset: %s %d" % (repr(Zustand), len(Zustand))) ziele = frozenset() for item in Zustand: ziele = ziele.union(self._delta(item, Zeichen)) return ziele else: self.log.warning("Zustand: nicht unterstuetzter Datentyp %s" % repr(Zustand)) raise ValueError() # Da Namen der Zustaende Strings sind, brauchen wir den Zustand auch als String try: stringZustand = list(Zustand)[0] except Exception, e: self.log.error("[%s] Zustand '%s' Ausgangszustand ist leere Menge!" % (self.name, repr(Zustand))) return frozenset([]) # Pruefen, ob der zu behandelnde Zustand ueberhaupt Teil der Zustandsmenge ist if not Zustand.issubset(self.S): # self.log.debug("Zustand '%s' nicht in der Zustandsmenge '%s' ?" % (Zustand, ','.join(sorted(self.S)))) raise NoSuchStateException(stringZustand, self.S) # Keine Regeln fuer Zustand definiert if not Zustand.issubset(self.delta): raise NoRuleForStateException(stringZustand, self.delta.keys()) for keyObject in self.delta[stringZustand].keys(): if Zeichen in keyObject: return self.delta[stringZustand][keyObject] # self.log.debug("Kein Folgezustand fuer '%s' von '%s'." % (Zeichen, Zustand)) return frozenset([])