有限狀態機(Python)

  有限狀態機(Finite-state machine, FSM),又稱有限狀態自動機,簡稱狀態機,是表示有限個狀態以及在這些狀態之間的轉移和動做等行爲的數學模型。FSM是一種算法思想,簡單而言,有限狀態機由一組狀態、一個初始狀態、輸入和根據輸入及現有狀態轉換爲下一個狀態的轉換函數組成。現實世界中存在大量具備有限個狀態的系統:鐘錶系統、電梯系統、交通訊號燈系統、通訊協議系統、正則表達式、硬件電路系統設計、軟件工程,編譯器等,有限狀態機的概念就是來自於現實世界中的這些有限系統。php

  通常能夠用狀態圖來對一個狀態機進行精確地描述。你們請看這個可樂機的狀態圖 。python

  從圖中就能夠清楚地看到可樂機的運行過程,圖中直觀地表現了可樂機投入不一樣金額硬幣時的情況以及幾個處理步驟的各個狀態和它們之間的轉換關係,根據投入硬幣的不一樣面值,對總金額進行計算,並對各類操做進行響應以完成一次購買。 狀態機的動態結構使得其在通信系統,數字協議處理系統,控制系統,用戶界面等領域獲得了廣泛地應用。git

  • 有限狀態機模型

有限狀態機是一個五元組$M=\left(Q,\Sigma ,\delta ,q_0,F\right)$,其中:github

$Q=\left\{q_0,q_1,\text{...},q_n\right\}$是有限狀態集合。在任一肯定的時刻,有限狀態機只能處於一個肯定的狀態$q_i$;正則表達式

$\Sigma =\left\{\sigma _1,\sigma _{2,\text{...},}\sigma _n\right\}$是有限輸入字符集合。在任一肯定的時刻,有限狀態機只能接收一個肯定的輸入$\sigma_j$;算法

$\delta :Q\times \Sigma \rightarrow Q$是狀態轉移函數,在某一狀態下,給定輸入後有限狀態機將轉入狀態遷移函數決定的一個新狀態;設計模式

$q_0\in Q$是初始狀態,有限狀態機由此狀態開始接收輸入;數組

$F\subseteq Q$是最終狀態集合,有限狀態機在達到終態後再也不接收輸入。app

  • 有限狀態機的實現

  有限狀態機有多種實現方式:框架

  1switch-case或if-else

  遊戲引擎是有限狀態機最爲成功的應用領域之一,因爲設計良好的狀態機可以被用來取代部分的人工智能算法,所以遊戲中的每一個角色或者器件都有可能內嵌一個狀態機。考慮RPG遊戲中城門這樣一個簡單的對象,它具備打開(Opened)、關閉(Closed)、上鎖(Locked)、解鎖(Unlocked)四種狀態。當玩家到達一個處於狀態Locked的門時,若是此時他已經找到了用來開門的鑰匙,那麼他就能夠利用它將門的當前狀態轉變爲Unlocked,進一步還能夠經過旋轉門上的把手將其狀態轉變爲Opened,從而成功地進入城內。

switch (state)  {
  // 處理狀態Opened的分支
  case (Opened): {
    // 執行動做Open
    open();
    // 檢查是否有CloseDoor事件
    if (closeDoor()) { 
      // 當前狀態轉換爲Closed
      changeState(Closed)
    }
    break;
  } 
  // 處理狀態Closed的分支
  case (Closed): {
    // 執行動做Close
    close();
    // 檢查是否有OpenDoor事件
    if (openDoor()) {
      // 當前狀態轉換爲Opened
      changeState(Opened);
    }
    // 檢查是否有LockDoor事件
    if (lockDoor()) {
      // 當前狀態轉換爲Locked
      changeState(Locked);
    }
    break;
  }
 
  // 處理狀態Locked的分支
  case (Locked): {
    // 執行動做Lock
    lock();
    // 檢查是否有UnlockDoor事件
    if (unlockDoor()) {
      // 當前狀態轉換爲Unlocked
      changeState(Unlocked);
    }
    break;
  }
 
  // 處理狀態Unlocked的分支
  case (Unlocked): {
    // 執行動做Unlock
    unlock();
    // 檢查是否有LockDoor事件
    if (lockDoor()) {
      // 當前狀態轉換爲Locked    
      changeState(Locked)
    }
    // 檢查是否有OpenDoor事件    
    if (openDoor()) {
      // 當前狀態轉換爲Opened
      changeSate(Opened);
    }
    break;
  } 
}
View Code

  當狀態量少而且各個狀態之間變化的邏輯比較簡單時,使用switch語句實現的有限狀態機的確可以很好地工做,但代碼的可讀性並不十分理想。在很長一段時期內,使用switch語句一直是實現有限狀態機的惟一方法,甚至像編譯器這樣複雜的軟件系統,大部分也都直接採用這種實現方式。但以後隨着狀態機應用的逐漸深刻,構造出來的狀態機愈來愈複雜,這種方法也開始面臨各類嚴峻的考驗,其中最使人頭痛的是若是狀態機中的狀態很是多,或者狀態之間的轉換關係異常複雜,那麼簡單地使用switch語句構造出來的狀態機將難以擴展和維護

  2. 狀態表

  維護一個二維狀態表,橫座標表示當前狀態,縱座標表示輸入,表中一個元素存儲下一個狀態和對應的操做。這一招易於維護,可是運行時間和存儲空間的代價較大。

  3. 使用宏定義描述狀態機

  4. 面向對象的設計模式

  一個簡單的例子:咱們想識別一句只包含有限個詞語的話表達的語氣。句子以"Python is"開頭,後面接着一個形容詞或是加not限定的形容詞。例如,

"Python is great"     → positive meaning
"Python is stupid"    → negative meaning
"Python is not ugly" → positive meaning

   首先定義一個StateMachine類

class StateMachine:
    def __init__(self): 
        self.handlers = {}        # 狀態轉移函數字典
        self.startState = None    # 初始狀態
        self.endStates = []       # 最終狀態集合
    
    # 參數name爲狀態名,handler爲狀態轉移函數,end_state代表是否爲最終狀態
    def add_state(self, name, handler, end_state=0):
        name = name.upper() # 轉換爲大寫
        self.handlers[name] = handler
        if end_state:
            self.endStates.append(name)

    def set_start(self, name):
        self.startState = name.upper()

    def run(self, cargo):
        try:
            handler = self.handlers[self.startState]
        except:
            raise InitializationError("must call .set_start() before .run()")
        if not self.endStates:
            raise  InitializationError("at least one state must be an end_state")
        
        # 從Start狀態開始進行處理
        while True: 
            (newState, cargo) = handler(cargo)     # 通過狀態轉移函數變換到新狀態
            if newState.upper() in self.endStates: # 若是跳到終止狀態,則打印狀態並結束循環
                print("reached ", newState)
                break 
            else:                        # 不然將轉移函數切換爲新狀態下的轉移函數 
                handler = self.handlers[newState.upper()]   

  而後自定義有限狀態和狀態轉移函數,並在main函數中開始進行處理:

from statemachine import StateMachine

# 有限狀態集合
positive_adjectives = ["great","super", "fun", "entertaining", "easy"]
negative_adjectives = ["boring", "difficult", "ugly", "bad"]

# 自定義狀態轉變函數
def start_transitions(txt):
    # 過指定分隔符對字符串進行切片,默認爲空格分割,參數num指定分割次數
    # 將"Python is XXX"語句分割爲"Python"和以後的"is XXX"
    splitted_txt = txt.split(None, 1)  
    word, txt = splitted_txt if len(splitted_txt) > 1 else (txt,"")
    if word == "Python":   
        newState = "Python_state" # 若是第一個詞是Python則可轉換到"Python狀態"
    else:
        newState = "error_state"  # 若是第一個詞不是Python則進入終止狀態
    return (newState, txt)        # 返回新狀態和餘下的語句txt

def python_state_transitions(txt):
    splitted_txt = txt.split(None,1)
    word, txt = splitted_txt if len(splitted_txt) > 1 else (txt,"")
    if word == "is":
        newState = "is_state"
    else:
        newState = "error_state"
    return (newState, txt)

def is_state_transitions(txt):
    splitted_txt = txt.split(None,1)
    word, txt = splitted_txt if len(splitted_txt) > 1 else (txt,"")
    if word == "not":
        newState = "not_state"
    elif word in positive_adjectives:
        newState = "pos_state"
    elif word in negative_adjectives:
        newState = "neg_state"
    else:
        newState = "error_state"
    return (newState, txt)

def not_state_transitions(txt):
    splitted_txt = txt.split(None,1)
    word, txt = splitted_txt if len(splitted_txt) > 1 else (txt,"")
    if word in positive_adjectives:
        newState = "neg_state"
    elif word in negative_adjectives:
        newState = "pos_state"
    else:
        newState = "error_state"
    return (newState, txt)


if __name__== "__main__": m = StateMachine() m.add_state("Start", start_transitions) # 添加初始狀態 m.add_state("Python_state", python_state_transitions) m.add_state("is_state", is_state_transitions) m.add_state("not_state", not_state_transitions) m.add_state("neg_state", None, end_state=1) # 添加最終狀態 m.add_state("pos_state", None, end_state=1) m.add_state("error_state", None, end_state=1) m.set_start("Start") # 設置開始狀態 m.run("Python is great") m.run("Python is not fun") m.run("Perl is ugly") m.run("Pythoniseasy")

  運行結果以下:

reached  pos_state
reached  neg_state
reached  error_state
reached  error_state

  能夠看到,這種有限狀態機的寫法,邏輯清晰,表達力強,有利於封裝事件。一個對象的狀態越多、發生的事件越多,就越適合採用有限狀態機的寫法。

  transitions是一個由Python實現的輕量級的、面向對象的有限狀態機框架。transitions最基本的用法以下,先自定義一個類,而後定義一系列狀態和狀態轉移(定義狀態和狀態轉移有多種方式,下面只寫了最簡明的一種,具體要參考文檔說明),最後初始化狀態機。

from transitions import Machine

# 定義一個本身的類
class Matter(object):
    pass
model = Matter()


# 狀態定義
states=['solid', 'liquid', 'gas', 'plasma']


# 定義狀態轉移
# The trigger argument defines the name of the new triggering method
transitions = [
    {'trigger': 'melt', 'source': 'solid', 'dest': 'liquid' },
    {'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas'},
    {'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas'},
    {'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma'}]


# 初始化
machine = Machine(model=model, states=states, transitions=transitions, initial='solid')


# Test 
model.state    # solid

# 狀體轉變
model.melt()

model.state   # liquid

 

 

參考:

http://blog.csdn.net/xgbing/article/details/2784127

http://blog.csdn.net/gzlaiyonghao/article/details/1510688

http://www.python-course.eu/finite_state_machine.php

https://wiki.python.org/moin/FiniteStateMachine

http://fsme.sourceforge.net/

https://github.com/tyarkoni/transitions

相關文章
相關標籤/搜索