实现一个非确定性下推自动机(DPDA - Deterministic PushDown Automaton)

一个非确定下推自动机,是一个在有限状态机的基础上,辅之以一个栈数据结构作为外部存储,而非确定性来自于DPDA每一步计算后,其机器配置的不确定性。

一个应用DPDA的典型例子是确定一个字符串是否是回文字符串,类似于aba, abba, babbaabbab等。下面我们就使用Python语言来一步步来实现一个简单的对于DPDA的模拟。

我们实现DPDA的第一步,首先要抽象出一个栈的数据结构,而为了保证每次操作相互之间不相互影响,我们在实现栈数据结构的时候,每种对于栈的操作都返回一个崭新的栈对象,其中的数据维持着栈操作后的最新状态。

Step1. 栈的实现

class Stack(object):
    def __init__(self, contents):
        self.contents = contents

    def push(self, character):
        return Stack([character] + self.contents)

    def pop(self):
        return Stack(self.contents[1:])

    def top(self):
        return self.contents[0]

    def __repr__(self):
        return '##'.format(self.top(), ''.join(self.contents[1:]))


这个栈的模拟是异常清晰的,我们使用一个列表类型的成员变量contents来作为栈的底层存储:

1. 入栈操作(push),我们将新的元素作为起点,拼接现有的元素列表,最终生成一个新的元素列表,最后使用这个新的元素列表构建出一个新的栈对象并返回。这个返回的栈对象反映了入栈操作之后的栈的最终状态。

2. 出栈操作(pop),除却底层列表的第一个元素之后,形成一个新的元素列表,最后使用这个新的元素列表构建出一个新的栈对象并返回。这个返回的栈对象反映了出栈操作之后的栈的最终状态;

3. 取栈顶元素(top),取到元素列表的第一个元素(也即栈顶元素),并返回。这个操作对于栈对象本身没有任何影响。


Step2. 下推自动机配置状态的抽象的实现

首先我们要明确什么是下推自动机的配置。我们知道下推自动机是一个辅之以栈结构的状态机,所以一个配置的构成应该包含一个状态标记(state)和一个栈实例(stack),其实其配置整体可以看成是一个大的状态。 为了适应下推自动机卡住的情况,我们增加了一个特殊的stuck状态,用于标识当前状态下,下推自动机不能继续执行。

class PDAConfiguration(object):
    STUCK_STATE = object()

    def __init__(self, state, stack):
        self.state = state
        self.stack = stack

    def stuck(self):
        return PDAConfiguration(self.STUCK_STATE, self.stack)

    def is_stuck(self):
        return self.state == self.STUCK_STATE

    def __repr__(self):
        return 'PDAConfiguration(state={}, stack={})'.format(self.state, self.stack)

    def __hash__(self):
        return hash((self.state, tuple(self.stack.contents)))

    def __eq__(self, other):
        return self.__hash__() == other.__hash__()

Step3. 对于下推自动机规则的抽象的实现

一台状态机必须会遵循许多规则来完成对状态的管理。一个状态机肯定又一个当前状态(state),并且其会读入输入(character),然后会根据当前状态和输入确定下一个状态(next_state)。以上的抽象规则是一个典型的状态机的规则。作为状态机增强版本的下推自动机,状态的转移不仅仅依赖于当前状态和输入,还依赖于一个栈的出入规则:也就是当前的栈顶元素(需要出栈的元素,每次状态转移必须做一次出栈操作)是什么(pop_character)和入栈规则是什么(push_characters)。

除了定义上述的几个基本成员变量之外,我们还需有方法来确定某个配置输入下,能否找到一条可以应用的规则。而这个确定过程就需要配置中的state (configuration.state)与本规则的state一致 AND 配置中栈的栈顶元素(configuration.stack.top())与本规则的栈顶元素假设(pop_character)相符合 AND 输入(character)与本规则的输入假设(character)相符合。

另一个重要的实现的点在于,如果本规则能够被应用,那么在应用本规则之后,我们期待得到什么?很明显,我们在应用一个规则之后需要希望得到一个具有新的状态的下推自动机。而这个状态的抽象也就是我们上面实现的PDAConfiguration类,所以我们需要最终得到一个PDAConfiguration类的实例。有了这个目标之后,我们就需要思考怎样去构建一个PDAConfiguration,从上一段的描述看,我们需要两个元素:state和stack,一个状态标记和一个对应的栈实例。对于本条规则而言,其下一个(follow)状态已经直接在规则中给出,也就是成员变量next_state;而next_stack并没有直接给出,需要一点计算(方法next_stack)来生成,以下是具体的实现:

class PDARule(object):
    def __init__(self, state, character, next_state, pop_character, push_characters):
        self.state = state
        self.character = character
        self.next_state = next_state
        self.pop_character = pop_character
        self.push_characters = push_characters
        self.push_characters.reverse()

    def applies_to(self, configuration, character):
        return all([self.state == configuration.state,
                    self.pop_character == configuration.stack.top(),
                    self.character == character])

    def follow(self, configuration):
        return PDAConfiguration(self.next_state, self.next_stack(configuration))

    def next_stack(self, configuration):
        popped_stack = configuration.stack.pop()

        for push_character in self.push_characters:
            popped_stack = popped_stack.push(push_character)
        return popped_stack

请注意,以上实现中用户给出的push_characters是按照栈的输入顺讯给出的,所以需要在初始化时将其反转,以恢复成栈的存储顺序。

Step4. 管理规则列表

我们既然已经有了规则抽象,同时一台下推自动机会有许多规则,而管理下推自动机的不确定性也需要小心管理,也就是要根据一组配置状态和当前的配置状态和输入得出下一组的配置状态,具体请看注释内容:

class NPDARulebook(object):
    def __init__(self, rules):
        """给出的所有规则"""
        self.rules = rules

    def next_configurations(self, configurations, character):
        """根据一组配置状态和一个输入,确定下一组配置状态,而所谓的不确定性就在于这个里的下一组配置状态可能有多个配置状态"""
        _configurations = set()
        for rs in map(lambda x: self.follow_rules_for(x, character), configurations):
            _configurations.update(rs)  # update accepts a iterable object to update the set
        return _configurations

    def follow_rules_for(self, configuration, character):
        """应用所有可应用的规则之后的所有的状态配置"""
        return map(lambda x: x.follow(configuration), self.rules_for(configuration, character))

    def rules_for(self, configuration, character):
        """返回所有可以应用于配置状态configuration和输入character的规则列表"""
        return [item for item in self.rules if item.applies_to(configuration, character)]

    def follow_free_moves(self, configurations):
        """自由移动:也就是所有输入为None的规则查找"""
        more_configurations = self.next_configurations(configurations, None)
        if more_configurations.issubset(configurations):
            return configurations
        else:
            return self.follow_free_moves(configurations.union(more_configurations))

Step5. 实现非确定性下推自动机

class NPDA(object):
    def __init__(self, current_configurations, accept_states, rulebook):
        self._current_configurations = current_configurations
        self.accept_states = accept_states
        self.rulebook = rulebook

    def accepting(self):
        """当前的任何配置状态为接受状态,则返回True"""
        return any(map(lambda x: x.state in self.accept_states, self.current_configurations))

    def read_character(self, character):
        """根据当前的一组配置状态和输入,获取新的一组当前状态"""
        self.current_configurations = self.rulebook.next_configurations(self.current_configurations, character)

    def read_string(self, _string):
        """便利方法"""
        for _char in _string:
            self.read_character(_char)

    @property
    def current_configurations(self):
        """注意:支持自由移动"""
        return self.rulebook.follow_free_moves(self._current_configurations)

    @current_configurations.setter
    def current_configurations(self, value):
        self._current_configurations = value

以下是对这个非确定性下推自动机的测试:

if __name__ == '__main__':
    rulebook = NPDARulebook([
        PDARule(1, 'a', 1, '$', ['a', '$']),
        PDARule(1, 'a', 1, 'a', ['a', 'a']),
        PDARule(1, 'a', 1, 'b', ['a', 'b']),
        PDARule(1, 'b', 1, '$', ['b', '$']),
        PDARule(1, 'b', 1, 'a', ['b', 'a']),
        PDARule(1, 'b', 1, 'b', ['b', 'b']),
        PDARule(1, None, 2, '$', ['$']),
        PDARule(1, None, 2, 'a', ['a']),
        PDARule(1, None, 2, 'b', ['b']),
        PDARule(2, 'a', 2, 'a', []),
        PDARule(2, 'b', 2, 'b', []),
        PDARule(2, None, 3, '$', ['$']),
    ])

    configuration = PDAConfiguration(1, Stack(['$']))
    import pprint
    npda = NPDA(set([configuration]), [3], rulebook)
    print(npda.accepting())
    pprint.pprint(npda.current_configurations)
    npda.read_string('abb')
    print(npda.accepting())
    pprint.pprint(npda.current_configurations)
    npda.read_string('a')
    print(npda.accepting())
    pprint.pprint(npda.current_configurations)

其输出为:

True
{PDAConfiguration(state=1, stack=##),
 PDAConfiguration(state=3, stack=##),
 PDAConfiguration(state=2, stack=##)}
False
{PDAConfiguration(state=1, stack=##),
 PDAConfiguration(state=2, stack=##),
 PDAConfiguration(state=2, stack=##)}
True
{PDAConfiguration(state=2, stack=##),
 PDAConfiguration(state=2, stack=##),
 PDAConfiguration(state=1, stack=##),
 PDAConfiguration(state=3, stack=##)}

Process finished with exit code 0
待续

你可能感兴趣的:(算法与数据结构)