自动机、状态机和状态模式

1,078 阅读8分钟

有限自动机与状态模式

前言

最近在学习编译原理的词法分析,其中讲到了有限自动机,自动机状态的切换让我想到了以前学习的一种设计模式即状态模式,两者似乎有相似之处,但又不同,于是我复习了一下状态模式,然后总结出了这篇文章。

一、有限自动机

概念:自动机(Automaton)是一个数学模型,用于描述系统的状态及其状态之间的转换。常见类型包括有限自动机(确定性有限自动机DFA和非确定性有限自动机NFA)、推理自动机(PDA)、图灵机等。自动机理论在计算机科学中的应用广泛,包括编译器设计、正则表达式匹配、自然语言处理等。

下面通过词法分析的例子来理解自动机:

int age = 10; 词法分析的过程就是要将这段代码拆成如下四个token,利用自动机的概念就可以很好的实现。

int
age
=
10

自动机拆分这段代码token的过程:

  1. 假如i、n、t分别对应int1、int2、int3三种状态,那么自动机初始化的时候拿到i产生int1状态,int1状态下拿到n得到int2状态,int2状态下拿到t得到int3状态,int3状态拿到空格生成一个token也就是int这个关键字并回到初始化的状态。
  2. 初始化的状态拿到a产生id的状态,如果后面都是字母或者数字那就都属于id,直到空格生成一个token也就是age这个变量并再次回到初始化状态。
  3. 初始化状态拿到=得到assignment状态,assignment状态拿到空格生成=的token并回到初始化状态。
  4. 初始化状态拿到1得到intLiteral字面量状态,intLiteral状态直到遇到冒号产生10这个token并回到初始状态。

截图

用swift实现代码大概如下:


class ViewController: NSViewController {
    


    override func viewDidLoad() {
        super.viewDidLoad()
        let code = "int age = 10;"
        let lexer = SimpleLexer()
        if let reader = lexer.tokenize(code) {
            dump(reader)
        }

        let code1 = "intA >= 10;"
        let lexer1 = SimpleLexer()
        if let reader1 = lexer1.tokenize(code1) {
            dump(reader1)
        }
    }

    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }

    func dump(_ reader: SimpleTokenReader) {
        while let token = reader.read() {
            print(token.text!)
        }
    }


}

class SimpleLexer {
    
    var tokenText: String? = ""
    var tokens: [Token]?
    var token: SimpleToken?
    
    func tokenize(_ script: String) -> SimpleTokenReader? {
        guard !script.isEmpty else { return nil }
        tokens = [Token]()
        token = SimpleToken()
        var currentIndex = script.startIndex
        var state: DfaState = initToken(script[currentIndex])
        while currentIndex < script.endIndex {
            currentIndex = script.index(after: currentIndex)
            guard currentIndex < script.endIndex else { break }
            let ch = script[currentIndex]
            switch state {
            case .initial:
                state = initToken(ch)
            case .id_int1:
                if ch == "n" {
                    state = .id_int2
                    token?.type = .int
                } else {
                    state = .id
                    token?.type = .identifier
                }
                tokenText?.append(ch)
            case .id_int2:
                if ch == "t" {
                    state = .id_int3
                    token?.type = .int
                } else {
                    state = .id
                    token?.type = .identifier
                }
                tokenText?.append(ch)
            case .id_int3:
                if isBlank(ch) {
                    state = initToken(ch)
                } else {
                    state = .id
                    token?.type = .identifier
                    tokenText?.append(ch)
                }
            case .id:
                if isAlpha(ch) || isDigit(ch) {
                    state = .id
                    token?.type = .identifier
                    tokenText?.append(ch)
                } else if isBlank(ch) {
                    state = initToken(ch)
                }
            case .assignment:
                if isBlank(ch) {
                    state = initToken(ch)
                }
            case .intLiteral:
                if isDigit(ch) {
                    state = .intLiteral
                    token?.type = .intLiteral
                    tokenText?.append(ch)
                } else {
                    state = initToken(ch)
                }
            case .gt:
                if ch == "=" {
                    state = .ge
                    token?.type = .ge
                    tokenText?.append(ch)
                }
            case .ge:
                state = initToken(ch)
            default:
                break
            }
        }
        let reader = SimpleTokenReader()
        reader.tokens = tokens!
        return reader
    }
    
    private func initToken(_ ch: Character) -> DfaState {
        if let tokenText = tokenText, !tokenText.isEmpty, let token = token {
            token.text = tokenText
            tokens?.append(token)
            self.token = SimpleToken()
            self.tokenText = ""
        }
        if isAlpha(ch) {
            if ch == "i" {
                token?.type = .int
                tokenText?.append(ch)
                return .id_int1
            } else {
                token?.type = .identifier
                tokenText?.append(ch)
                return .id
            }
        } else if ch == "=" {
            token?.type = .assignment
            tokenText?.append(ch)
            return .assignment
        } else if isDigit(ch) {
            token?.type = .intLiteral
            tokenText?.append(ch)
            return .intLiteral
        } else if ch == ">" {
            token?.type = .gt
            tokenText?.append(ch)
            return .gt
        }
        return .initial
    }

    private func isAlpha(_ ch: Character) -> Bool {
        return (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z")
    }
    
    private func isDigit(_ ch: Character) -> Bool {
        return ch >= "0" && ch <= "9"
    }

    private func isBlank(_ ch: Character) -> Bool {
        return ch == " " || ch == "\t" || ch == "\n"
    }
}

protocol Token {
    var type: TokenType { get set }
    var text: String? { get set }
}

class SimpleToken: Token {
    var type: TokenType = .identifier
    var text: String?
}

protocol TokenReader {
    func read() -> Token?
    func speek() -> Token?
    func unread() -> Token?
    func getPosition() -> Int
    func setPosition(_ value: Int)
}

class SimpleTokenReader: TokenReader {
    
    var tokens: [Token] = []
    
    var position: Int = -1
    
    func read() -> (any Token)? {
        position += 1
        guard position > -1, position < tokens.count else { return nil }
        return tokens[position]
    }
    
    func speek() -> (any Token)? {
        guard position < tokens.count else { return nil }
        position += 1
        return tokens[position]
    }
    
    func unread() -> (any Token)? {
        position -= 1
        guard position > -1, position < tokens.count else { return nil }
        return tokens[position]
    }
    
    func getPosition() -> Int {
        position
    }
    
    func setPosition(_ value: Int) {
        position = value
    }
    
}

/**
 * Token的类型
 */
enum TokenType {
    case plus           // +
    case minus          // -
    case star           // *
    case slash          // /

    case ge             // >=
    case gt             // >
    case eq             // ==
    case le             // <=
    case lt             // <

    case semiColon      // ;
    case leftParen      // (
    case rightParen     // )

    case assignment     // =

    case `if`
    case `else`
    
    case int

    case identifier     // 标识符

    case intLiteral     // 整型字面量
    case stringLiteral  // 字符串字面量
}

private enum DfaState {
    case initial

    case `if`, id_if1, id_if2, `else`, id_else1, id_else2, id_else3, id_else4, `int`, id_int1, id_int2, id_int3, id, gt, ge

    case assignment

    case plus, minus, star, slash

    case semiColon
    case leftParen
    case rightParen

    case intLiteral
}

简单来说自动机词法分析上就是根据上一次的状态和当前拿到的字符生成新的状态,每生成一个token回到初始状态,循环往复直到解析出所有token。

自动机的组成总结如下:

状态(States):系统可能处于的不同状态。 输入符号(Input Symbols):驱动状态转换的外部事件或条件。 转换函数(Transition Function):定义了每个状态在给定输入下转换到下一个状态的规则。 初始状态(Initial State):系统开始时所处的状态。 接受状态(Accept States):表示输入序列被接受的终止状态。

二、状态机

概念: 状态机(State Machine)是一种用于建模系统行为的抽象模型。它由一组状态和一组引起状态转换的事件组成。状态机描述了一个系统在不同状态下的行为和状态之间的转换规则。状态机在计算机科学、工程和许多其他领域中都有广泛应用。

一个状态机通常包括以下几个主要部分:

  1. 状态(States):

    系统可能处于的不同状态的集合。每个状态表示系统在某一时刻的情况或配置。

  2. 事件(Events):

    导致状态转换的外部输入或内部条件。

  3. 转换(Transitions):

定义状态之间的转换规则,即在某一状态下接收到某一事件时如何转换到下一个状态。

  1. 初始状态(Initial State):

系统开始时所处的状态。

  1. 终止状态(Final States)(可选):

系统可能终止的状态。

示例:自动售货机 下面是一个简单的自动售货机的状态机示例:

  • 状态:

等待投币(Waiting for Coin) 等待选择(Waiting for Selection) 出货(Dispensing) 退币(Returning Coin)

  • 事件:

投币(Insert Coin) 选择商品(Select Product) 出货完成(Dispense Complete) 取消(Cancel)

  • 转换规则:

在“等待投币”状态下,收到“投币”事件,转换到“等待选择”状态。 在“等待选择”状态下,收到“选择商品”事件,转换到“出货”状态。 在“出货”状态下,收到“出货完成”事件,转换到“等待投币”状态。 在“等待选择”状态下,收到“取消”事件,转换到“退币”状态,然后转换到“等待投币”状态。

状态机和自动机在概念和应用上有相似之处,但各自有不同的侧重点和应用场景。状态机更广泛应用于各种工程和软件系统中,用于管理系统的状态和行为;自动机则更专注于形式语言和理论计算的研究,适用于处理和识别语言模式。了解它们各自的特点和应用领域,有助于在实际问题中选择合适的工具和方法。

三、状态模式

概念:状态模式是一种面向对象的设计模式,用于对象在不同状态下改变其行为。通过将状态的行为封装到独立的状态类中,客户端对象的行为随状态对象的变化而变化。状态模式也可以作为状态机的一种实现方式。适合对于状态并不多、状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能比较复杂的状态机来说,我们首选这种实现方式

组成部分:

  • 状态接口(State Interface):定义了与状态相关的行为。
  • 具体状态类(Concrete State Classes):实现了状态接口,并且包含状态的具体行为和状态转换逻辑。
  • 上下文类(Context Class):管理当前状态并在状态改变时切换到相应的状态类。

应用领域:

  • 面向对象编程中的状态管理:对象在不同状态下表现出不同的行为。
  • 游戏开发:角色状态管理(如行走、奔跑、跳跃)。
  • 图形用户界面:按钮状态管理(如启用、禁用、悬停)。

实现方式:

  • 每个状态实现为一个独立的类,这些类实现同一个状态接口。

  • 上下文类持有一个状态对象,并将状态相关的行为委托给当前的状态对象来执行。

自动售货机用状态模式实现:

protocol VendingMachineState {
    func insertCoin()
    func selectProduct()
    func dispenseProduct()
    func cancel()
}

class WaitingForCoinState: VendingMachineState {
    private let vendingMachine: VendingMachine

    init(vendingMachine: VendingMachine) {
        self.vendingMachine = vendingMachine
    }

    func insertCoin() {
        print("Coin inserted. Please select a product.")
        vendingMachine.setState(vendingMachine.waitingForSelectionState)
    }

    func selectProduct() {
        print("You need to insert a coin first.")
    }

    func dispenseProduct() {
        print("You need to insert a coin and select a product first.")
    }

    func cancel() {
        print("No operation to cancel.")
    }
}

class WaitingForSelectionState: VendingMachineState {
    private let vendingMachine: VendingMachine

    init(vendingMachine: VendingMachine) {
        self.vendingMachine = vendingMachine
    }

    func insertCoin() {
        print("Coin already inserted. Please select a product.")
    }

    func selectProduct() {
        print("Product selected. Dispensing product...")
        vendingMachine.setState(vendingMachine.dispensingState)
    }

    func dispenseProduct() {
        print("Please select a product first.")
    }

    func cancel() {
        print("Operation cancelled. Returning coin...")
        vendingMachine.setState(vendingMachine.waitingForCoinState)
    }
}

class DispensingState: VendingMachineState {
    private let vendingMachine: VendingMachine

    init(vendingMachine: VendingMachine) {
        self.vendingMachine = vendingMachine
    }

    func insertCoin() {
        print("Please wait, dispensing product...")
    }

    func selectProduct() {
        print("Please wait, dispensing product...")
    }

    func dispenseProduct() {
        print("Product dispensed. Thank you!")
        vendingMachine.setState(vendingMachine.waitingForCoinState)
    }

    func cancel() {
        print("Cannot cancel, dispensing product...")
    }
}

class VendingMachine {
    let waitingForCoinState: VendingMachineState
    let waitingForSelectionState: VendingMachineState
    let dispensingState: VendingMachineState

    private var currentState: VendingMachineState

    init() {
        self.waitingForCoinState = WaitingForCoinState(vendingMachine: self)
        self.waitingForSelectionState = WaitingForSelectionState(vendingMachine: self)
        self.dispensingState = DispensingState(vendingMachine: self)
        self.currentState = waitingForCoinState
    }

    func setState(_ state: VendingMachineState) {
        self.currentState = state
    }

    func insertCoin() {
        currentState.insertCoin()
    }

    func selectProduct() {
        currentState.selectProduct()
    }

    func dispenseProduct() {
        currentState.dispenseProduct()
    }

    func cancel() {
        currentState.cancel()
    }
}

let vendingMachine = VendingMachine()

vendingMachine.insertCoin()       // Output: Coin inserted. Please select a product.
vendingMachine.selectProduct()    // Output: Product selected. Dispensing product...
vendingMachine.dispenseProduct()  // Output: Product dispensed. Thank you!

vendingMachine.insertCoin()       // Output: Coin inserted. Please select a product.
vendingMachine.cancel()           // Output: Operation cancelled. Returning coin...

vendingMachine.selectProduct()    // Output: You need to insert a coin first.
vendingMachine.insertCoin()       // Output: Coin inserted. Please select a product.
vendingMachine.cancel()           // Output: Operation cancelled. Returning coin...