算法之有限状态机,kmp算法理解

459 阅读5分钟

有限状态机

一、介绍

有限状态机(英语:Finite-state machine, FSM)是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
在计算机科学中, 有限状态机被广泛用于建模应用行为、硬件电路系统设计、软件工程,编译器、网络协议、和计算与语言的研究。

定义

下面是三个特征和四个要素

  • 状态总数是有限的。
  • 任一时刻,只处在一种状态之中(现态)。
  • 某种条件下(条件),会从一种状态转变(动作)到另一种状态(新态)。

举例

举例:菜单鼠标悬停:菜单显示,鼠标移开:菜单隐藏。

  var menu = {
    // 当前状态
    currentState: 'hide',
    // 绑定事件
    initialize: function() {
      var self = this;
      self.on("hover", self.transition);
    },
    // 状态转换
    transition: function(event){
      switch(this.currentState) {
        case "hide":
          this.currentState = 'show';
          doSomething();
          break;
        case "show":
          this.currentState = 'hide';
          doSomething();
          break;
        default:
          console.log('Invalid State!');
          break;
      }
    }
  };

// 交通信号灯(红绿灯)可以这样描述:
var fsm = StateMachine.create({
    initial: 'green',
    events: [
      { name: 'warn',  from: 'green',  to: 'yellow' },
      { name: 'stop', from: 'yellow', to: 'red' },
      { name: 'ready',  from: 'red',    to: 'yellow' },
      { name: 'go', from: 'yellow', to: 'green' }
    ]
  });
// 一个有限状态机的函数库
import StateMachine from "javascript-state-machine";

// 状态机模型
let fsm = new StateMachine({
  init: "pending",
  transitions: [
    { name: "resolve", from: "pending", to: "fulfilled" },
    { name: "reject",from: "pending", to: "rejected" }
  ],
  methods: {
    onResolve: function(state, data) {
      // state - 当前状态机实例,data - fsm.resolve(xxx)
      data.successList.forEach(fn=>fn())
    },
    onReject: function(state, data) {
      data.failList.forEach(fn=>fn())
    }
  }
});

// 定义Promise
class MyPromise {
  constructor(fn) {
    this.sucessList = []
    this.failList = []
    fn(
      function() {
        // resolve函数
        fsm.resolve(this)
      },
      function() {
        // reject函数
        fsm.reject(this)
      }
    );
      then(successFn, failFn){
        this.successList.push(successFn)
        this.failList.push(failFn)
      }
  }
}

// 测试代码
function loadImg(src) {
  const promise = new Promise(function(resolve, reject) {
    let img = document.createElement("img");
    img.onload = function() {
      resolve(img);
    };
    img.onerror = function() {
      reject();
    };
    img.src = src;
  });
  return promise;
}

let src
let result = loadImg(src)

result.then(function(){
  console.log('ok1')
}, function () {
  console.log('fail1')
})
result.then(function () {
  console.log('ok2')
}, function () {
  console.log('fail2')
})


二、JavaScript的有限状态机

每一个状态都是一个机器

  • 在每一个机器里,我们可以做计算、存储、输出......
  • 所有的这些机器接受的输入是一致的
  • 状态机的每一个机器本身没有状态,如果我们用函数来表示的话,它应 该是纯函数(无副作用)

每一个机器知道下一个状态

  • 每个机器都有确定的下一个状态(Moore)
  • 每个机器根据输入决定下一个状态(Mealy)

JavaScript的有限状态机

//每个函数是一个状态 
function state(input) //函数参数就是输入 
{ 
  //在函数中,可以自由地编写代码,处理每个状态的逻辑 return next;//返回值作为下一个状态 
}

//------以下是调用---------
while(input) { 
  //获取输入 state = state(input); //把状态机的返回值作为下一个状态 
}

三、用javascript有限状态机处理字符串

不使用状态机处理字符串

* a. 在一个字符串中,找到字符"a"
```javascript
      function match(str) {
        for(let i of str) {
            if (i === 'a') {
                return true
            }
        }
        return false
      }
      console.log(match('yes, I am'))
```  
* b. 不准使用正则表达式,纯粹用 JavaScript 的逻辑实现:在一个字符串中,找到字符“ab”  
```javascript
    function match(str) {
        let foundA = false;
        for (let c of str) {
            if (c == "a")
                foundA = true
            else if(foundA && c == "b")
                return true
            else foundA = false
        }
        return false
    }

    match('about to do')
```  
* c. 不准使用正则表达式,纯粹用 JavaScript 的逻辑实现:在一个字符串中,找到字符“abcdef”  
```javascript
    function match(str) {
        let foundA = false;
        let foundB = false;
        let foundC = false;
        let foundD = false;
        let foundE = false;
        for (let c of str) {
            if (c == "a")
                foundA = true
            else if(foundA && c == "b")
                foundB = true
            else if(foundB && c == "c")
                foundC = true
            else if(foundC && c == "d")
                foundD = true
            else if(foundD && c == "e")
                foundE = true
            else if(foundE && c == "f")
                return true
            else {
                let foundA = false;
                let foundB = false;
                let foundC = false;
                let foundD = false;
                let foundE = false;
            }
        }
        return false
    }
    match('abcdefa')
```

使用状态机处理字符串

  • a. 在一个字符串中,找到字符“abcdef”
        function match(string) {
            let state = start;
            for(let c of string){
                state = state(c)
            }
            return state == end;
        }
    
        function start(c) {
            if(c === "a") 
                return foundA
            else
              return start;
        }
    
        function end(c) {
            return end;
        }
    
        function foundA(c) {
            if(c === "b")
                return foundB;
            else
                return start(c);
        }
    
        function foundB(c) {
            if(c === "c")
                return foundC;
            else
                return start(c);
        }
    
        function foundC(c) {
            if(c === "d")
                return foundD;
            else
                return start(c);
        }
    
        function foundD(c) {
            if(c === "e")
                return foundE;
            else
                return start(c);
        }
        
        function foundE(c) {
            if(c === "f")
                return end;
            else
                return start(c);
        }
    
  • b. 用状态机实现:字符串“abcabx”的解析
        function match(string) {
            let state = start;
            for(let c of string) {
                state = state(c);
            }
            return state === end
        }
    
        function start(c) {
            if (c === 'a') 
                return foundA;
            else 
                return start
        }
    
        function end(c) {
            return end
        }
    
        function foundA(c) {
            if (c === 'b')
                return foundB
            else
                return start(c)
        }
    
        function foundB(c) {
            if (c==='c')
                return foundC
            else 
                return start(c)
        }
    
        function foundC(c) {
            if (c === 'a')
                return foundA2
            else
                return start(c)
        }
    
        function foundA2(c) {
            if (c==='b')
                return foundB2
            else
                return foundA(c)
        }
    
        function foundB2(c) {
            if (c==='x')
                return foundX(c)
            else
                return foundB(c)
        }
    
        function foundX(c) {
            if (c==='x')
                return end
            else
                return start(c)
        }
    
        console.log(match('ababcabx'))
    

四、状态机与KMP算法

  • 如何处理完全未知的 pattern; KMP算法; 分为以下两步:(关于kmp和有限状态机的关系参考www.cnblogs.com/techyu/p/15…

      1. 求跳转表格(对应的下标)
        table.png
      1. 求真正匹配
        function KMP(source, pattern){
            // 计算跳转表格
            let table = new Array(pattern.length).fill(0)
            {
                // i标识开始,j表示字重复
                let i = 1, j = 0;
                while(i < pattern.length) {
                    if (pattern[i] === pattern[j]) {
                        ++j, ++i;
                        // 前面匹配到了,所以+1,便于后面直接匹配之后的值
                        table[i] = j
                    } else {
                        // 说明没有匹配到,所以要跳转到对应的位置。如果是0,则重新匹配
                        if (j > 0)
                            j = table[j]
                        else {
                            ++i;
                        }
                    }
                }
                console.log(table)
            }
    
            // i source串位置, j pattern 位置
            {
                let i = 0; j = 0;
                while(i < source.length) {
                    if (pattern[j] === source[i]) {
                        ++i, ++j;
                    } else {
                        // 如果前面有重复的
                        if (j > 0)
                            j = table[j]
                        else 
                            ++i;
                    }
                    if (j === pattern.length) {
                        return true
                    }
                }
                return false
            }
            
            // abcdabce
            // aabaaac
            // 匹配
        }
    
        // 
        // ['a', 'b', 'c', 'd', 'a', 'b', 'c', 'e']
        // [ 0,   0,   0,   0,   0,   1,   2,   3]
    
        //0  abcdabce  
        //0  bcdabce  
        //0  cdabce  
        //0  dabce  
        //0  abce  
        //1  bce  
        //2  ce  
        //3  e  
    
        // KMP("", "abcdabce") // [0, 0, 0, 0, 0, 1, 2, 3]
        // KMP("", "abababc")  // [0, 0, 0, 1, 2, 3, 4]
        // KMP("", "aabaaac")  // [0, 0, 1, 0, 1, 2, 2]
        // console.log(KMP("", "abcdabce") )
    
    

五、有限状态机与正则的区别

      // 正则匹配(简单方便)
      const regExp = /<\s*(\S+)(\s[^>]*)?>[\s\S]*<\s*\/\1\s*>/

      // 状态机匹配(功能强大, 可以解析dom,可在匹配的同时做一些操作)
      const EOF = Symbol("EOF"); //EOF: end of file
      let currentToken = null;
      let stack = [{type: 'document', children: []}]
      let currentTextNode = null;

      function emit(token) {
          let top = stack[stack.length - 1];
          if(token.type == "startTag") {
              let element = {
                  type: 'element',
                  children: [],
              };
              element.tagName = token.tagName;
              top.children.push(element);
              element.parent = top;
              if(!token.isSelfClosing) {
                  stack.push(element)
              }
              currentTextNode = null;
          } else if(token.type == "endTag") {
              if(top.tagName != token.tagName) {
                  throw new Error("Tag start end doesnot match!")
              } else {
                  stack.pop();
              }
              currentTextNode = null;
          } else if(token.type === "text") {
              if(currentTextNode == null) {
                  currentTextNode = {
                      type: 'text',
                      content: ""
                  }
                  top.children.push(currentTextNode);
              }
              currentTextNode.content += token.content;
          }
      }

      function data(c) {
          if (c == "<") {
              return tagOpen;
          } else if (c == EOF) {
              emit({
                  type: "EOF"
              })
              return;
          } else {
              emit({
                  type: 'text',
                  content: c
              })
              return data
          }
      }

      function tagOpen(c) {
          if(c == '/') {
              return endTagOpen;
          } else if(c.match(/^[a-zA-Z]$/)) {
              currentToken={
                  type: 'startTag',
                  tagName: ""
              }
              return tagName(c)
          } else {
              emit({
                  type: 'text',
                  content: c
              })
              return ;
          }
      }

      function endTagOpen(c) {
          if(c.match(/^[a-zA-Z]$/)) {
              currentToken = {
                  type: 'endTag',
                  tagName: ""
              }
              return tagName(c)
          } else if(c == ">") {

          } else if(c == EOF) {

          } else {

          }
      }


      function tagName(c) {
          if(c.match(/^[\t\n\f ]$/)){
              // return beforeAttributeName;
          } else if(c == '/') {
              // return selfClosingStartTag;
          } else if(c.match(/^[a-zA-Z]$/)){
              currentToken.tagName += c //.toLowerCase();
              return tagName;
          } else if(c == ">") {
              emit(currentToken)
              return data;
          } else {
              currentToken.tagName += c //.toLowerCase();
              return tagName;
          }
      }


      const getDom = (html) => {
          let state = data;
          for(let c of html) {
              state = state(c)
          }
          state = state(EOF)
          return stack[0]
      }
      getDom("<h1>abc<span>def</span></h1>")

总结

  • 优势: 逻辑清晰,表达力强,有利于封装事件。一个对象的状态越多,发生的事件越多,越适合采用有限状态机写法。实际上promise也可以用有限状态机模型实现。
  • 一个非常有用的模型,可以模拟世界上大部分事物。
  • 在游戏(人物连续跳跃状态下不可持续跳跃,需要先进入落地状态),正则,编译器等方面应用广泛