Как конечные автоматы реализованы в коде?

Как реализовать dfa или nfa в коде Python?

Какие есть хорошие способы сделать это в python? И используются ли они когда-нибудь в реальных проектах?


person user5899005    schedule 08.02.2016    source источник
comment
Этот вопрос очень широкий. Скорее всего, он будет закрыт, если вы не сузите его до конкретного вопроса, на который можно дать объективный ответ. Во всяком случае... Да, они используются в реальных проектах. Конечный автомат является распространенным методом реализации. Вот один из возможных подходов в python: python-3-patterns -idioms-test.readthedocs.org/en/latest/   -  person John Hascall    schedule 08.02.2016
comment
Настоящие регулярные выражения могут быть сопоставлены DFA; на самом деле любой регулярный язык может быть представлен либо как регулярное выражение, либо как DFA.   -  person chepner    schedule 08.02.2016


Ответы (6)


Простой способ представить DFA — это словарь словарей. Для каждого состояния создайте словарь с ключами букв алфавита, а затем глобальный словарь с ключами состояний. Например, следующий DFA из статьи Википедии о DFA

введите описание изображения здесь

может быть представлен словарем следующим образом:

dfa = {0:{'0':0, '1':1},
       1:{'0':2, '1':0},
       2:{'0':1, '1':2}}

«Запустить» dfa против входной строки, взятой из рассматриваемого алфавита (после указания начального состояния и набора принимаемых значений), просто:

def accepts(transitions,initial,accepting,s):
    state = initial
    for c in s:
        state = transitions[state][c]
    return state in accepting

Вы начинаете с начального состояния, шаг за шагом проходите по строке символ за символом и на каждом шаге просто ищете следующее состояние. Когда вы закончите перебирать строку, вы просто проверяете, находится ли конечное состояние в наборе допускающих состояний.

Например

>>> accepts(dfa,0,{0},'1011101')
True
>>> accepts(dfa,0,{0},'10111011')
False

Для NFA вы можете хранить наборы возможных состояний, а не отдельные состояния в словарях переходов, и использовать модуль random для выбора следующего состояния из набора возможных состояний.

person John Coleman    schedule 08.02.2016
comment
Спасибо друг. Это был отличный ответ. - person user5899005; 09.02.2016

Здесь я представляю рекурсивное решение для NFA. Рассмотрим следующий nfa:

введите здесь описание изображения

Переходы могут быть представлены с помощью списка списков следующим образом:

transition = [[[0,1],[0]], [[4],[2]], [[4],[3]], [[4],[4]],[[4],[4]]]

Примечание. Состояние 4 является гипотетическим. Как только вы войдете в это состояние, вы не сможете двигаться дальше. Это полезно, когда вы не можете прочитать ввод из текущего состояния. Вы сразу переходите в состояние 4 и говорите, что ввод не принят для текущего прогресса (проверьте другие возможности, вернувшись назад). например, если вы находитесь в состоянии q1, а текущим входным символом является 'a', вы переходите в состояние 4 и прекращаете дальнейшие вычисления.

Вот код Python:

#nfa simulation for (a|b)*abb
#state 4 is a trap state


import sys

def main():
    transition = [[[0,1],[0]], [[4],[2]], [[4],[3]], [[4],[4]]]
    input = raw_input("enter the string: ")
    input = list(input) #copy the input in list because python strings are immutable and thus can't be changed directly
    for index in range(len(input)): #parse the string of a,b in 0,1 for simplicity
        if input[index]=='a':
            input[index]='0' 
        else:
            input[index]='1'

    final = "3" #set of final states = {3}
    start = 0
    i=0  #counter to remember the number of symbols read

    trans(transition, input, final, start, i)
    print "rejected"



def trans(transition, input, final, state, i):
    for j in range (len(input)):
        for each in transition[state][int(input[j])]: #check for each possibility
            if each < 4:                              #move further only if you are at non-hypothetical state
                state = each
                if j == len(input)-1 and (str(state) in final): #last symbol is read and current state lies in the set of final states
                    print "accepted"
                    sys.exit()
                trans(transition, input[i+1:], final, state, i) #input string for next transition is input[i+1:]
        i = i+1 #increment the counter


main()

Образец выполнения (допускаются строки, заканчивающиеся на ab):

enter the string: abb
accepted

enter the string: aaaabbbb
rejected
person Community    schedule 07.09.2017

Вот моя версия реализации dfa, если вы ищете более объектно-ориентированную. Однако я был немного вдохновлен ответом Джона Коулмана.

class Node:
    def __init__(self, val):
        self.val = val
        self.links = []
    def add_link(self, link):
        self.links.append(link)
    def __str__(self):
        node = "(%s):\n" % self.val
        for link in self.links:
            node += "\t" + link + "\n"
        return node
    def __add__(self, other):
        return str(self) + other
    def __radd__(self, other):
        return other + str(self)
    def equals(self, node):
        ok = (self.val == node.val)
        if len(self.links) == len(node.links):
            for i in range(len(self.links)):
                ok = ok and (self.links[i] == node.links[i])
            return ok
        else:
            return False

class Link:
    def __init__(self, from_node, etiquette, to_node):
        self.from_node = from_node
        self.etiquette = etiquette
        self.to_node = to_node
    def __str__(self):
        return "(%s --%s--> %s)" % (self.from_node.val, self.etiquette, self.to_node.val)
    def __add__(self, other):
        return str(self) + other
    def __radd__(self, other):
        return other + str(self)
    def equals(self, link):
        return (self.from_node == link.from_node) and (self.etiquette == link.etiquette) and (self.to_node == link.to_node)

class Automata:
    def __init__(self, initial_node, nodes, terminal_node):
        self.initial_node = initial_node
        self.nodes = nodes
        self.terminal_node = terminal_node
    def get_next_node(self, current_node, etiquette):
        for link in current_node.links:
            if link.etiquette == etiquette:
                return link.to_node
        return None
    def accepts(self, string):
        node = self.initial_node
        for character in string:
            node = self.get_next_node(node, character)
        return self.terminal_node.equals(node)
    def __str__(self):
        automata = "Initial node: %s\nTerminal node: %s\n" % (self.initial_node.val, self.terminal_node.val)
        for node in self.nodes:
            automata += node
        return automata
    def __add__(self, other):
        return str(self) + other
    def __radd__(self, other):
        return other + str(self)




if __name__ == '__main__':
    pass

    s0 = Node("s0")
    s1 = Node("s1")
    s2 = Node("s2")

    s0_0_s0 = Link(s0, '0', s0)
    s0_1_s1 = Link(s0, '1', s1)
    s1_0_s2 = Link(s1, '0', s2)
    s1_1_s0 = Link(s1, '1', s0)
    s2_0_s1 = Link(s2, '0', s1)
    s2_1_s2 = Link(s2, '1', s2)

    s0.add_link(s0_0_s0)
    s0.add_link(s0_1_s1)
    s1.add_link(s1_0_s2)
    s1.add_link(s1_1_s0)
    s2.add_link(s2_0_s1)
    s2.add_link(s2_1_s2)

    a = Automata(s0, [s0, s1, s2], s0)

    print(a)
    print(a.accepts('1011101')) #True
    print(a.accepts('10111011')) #False
person Chihab    schedule 14.12.2018

Вам не нужен цикл for по диапазону (len (input)), если вы используете рекурсию. Вы слишком усложняете код. Вот упрощенная версия

import sys

def main():
    transition = [[[0,1],[0]], [[4],[2]], [[4],[3]], [[4],[4]]]
    input = raw_input("enter the string: ")
    input = list(input) #copy the input in list because python strings are immutable and thus can't be changed directly
    for index in range(len(input)): #parse the string of a,b in 0,1 for simplicity
        if input[index]=='a':
            input[index]='0' 
        else:
            input[index]='1'

    final = "3" #set of final states = {3}
    start = 0

    trans(transition, input, final, start)
    print "rejected"


def trans(transition, input, final, state):
    for each in transition[state][int(input[0])]: #check for each possibility       
        if each < 4:                              #move further only if you are at non-hypothetical state
            state = each
            if len(input)==1:
                if (str(state) in final): #last symbol is read and current state lies in the set of final states
                    print "accepted"
                    sys.exit()
                else:
                    continue
            trans(transition, input[1:], final, state) #input string for next transition is input[i+1:]

main()
person SSPdude    schedule 21.10.2018

Я реализовал dfa, который работает для любого из dfa. Но это не объектно-ориентированный шаблон.

states=list(map(int,input("Enter States : ").split(" ")))
symbols=list(input("Enter Symbols : ").split(" "))
initial_state=int(input("Enter initial state : "))
final_states=list(map(int,input("Enter final states : ").split(" ")))

transitions=[]
inlists=[]

for i in range(len(symbols)):
    print("Enter transitions for symbol "+symbols[i]+" from all states :")
    for j in range(len(states)):
        inlists.append(int(input()))
    transitions.append(inlists)
    inlists=[]
cur_state=initial_state

while(1):
    cur_state=initial_state
    string=input("Enter string : ")

    if string=='#':
        break

    for symbol in string:
        print("["+str(cur_state)+"]"+"-"+symbol+"->",end="")
        cur_state=transitions[symbols.index(symbol)][cur_state]

    if cur_state in final_states:
        print("["+str(cur_state)+"]")
        print("String is accepted.")
    else:
        print("["+str(cur_state)+"]")
        print("String is not accepted.")
person Rohan Borkhataria    schedule 04.07.2019

Принятие модификации строки 101* и 001* @John Coleman

#Dfa для приема только 101+00101001

dfa101 = {0:{'1':1},
       1:{'0':2},
       2:{'1':3},
       3:{'0':3, '1':3}}

#Dfa for accepting only 001+00101001
dfa001={0:{'0':1},
       1:{'0':2},
       2:{'1':3},
       3:{'0':3, '1':3}}



def accepts(transitions,initial,accepting,s):
    state = initial
    try:
        for c in s:
            state = transitions[state][c]
        if(state in accepting):
            return 'Accepted'
        else:
            return 'Rejected'
    except:
        return 'Rejected'
print('Dfa of 101+ ',accepts(dfa101,0,{3},'10101111000')) #Accepted

print('Dfa of 001+ ',accepts(dfa001,0,{3},'00101010101')) #Accepted
person Saad Abbasi    schedule 12.01.2021