Beispiel #1
0
 def _initLogging(self):
     self.log = AutomatLogger().log
Beispiel #2
0
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([])