玩转状态转换机-附JS例子

文章目录

    • 两种状态表法
    • FSM的玩法:
    • 有限状态机的三种实现方式:
    • 状态表法(推荐)
      • 状态转移表:是个数组:
      • 状态动作表:
    • 实践一次
    • 重读状态机

本算法被应用在了 程序员学英语 项目中对缩小单词进行拆分的任务上. eg: getElementById -> get element by id
为了更精确的对单词频率进行统计,产生了该需求.
实际项目中的代码见:
https://github.com/dalerkd/Programmer-Study-English/commit/d0b4f6ddc2c890b60e29490cd48e368090f304b0


昨天(2019年4月28日周日)我首次使用 状态机 实现了一个将 英文大小写混拼字符串转换为多个单词的任务.
eg: getElementById 拆分出: get element by id.
当时使用的是if-else感觉很是比较复杂的.

今天在其基础上 复习和学习了 状态机的玩法.特别是表格数组 - 状态表法在具体实现上的使用.

两种状态表法

第一种是 比较推荐的: 当前状态 和 事件 作为坐标.
交叉处是 函数 和下一个状态.

FSM的玩法:

参考文档:https://blog.csdn.net/younggift/article/details/35848677
https://yunjianfei.iteye.com/blog/2061790

有限状态机的三种实现方式:

  1. switch-case/if-else
    最直观,庞大状态机难以维护.
  2. 状态表
    维护一个二维状态表.横坐标表示当前状态,
    纵坐标表示输入,表中:
    一个元素存储下一个状态和对应的操作.
  3. State Pattern
    状态模式

状态表法(推荐)

优点是:增加状态和事件都很方便维护.

查两张表:

  1. 状态转移表
    用 现态 和 条件,以及哪张表 查询,获得下一个状态 为当前状态.
  2. 状态动作表
    用 当前状态 查询
    获得函数并执行之.

状态转移表:是个数组:

现态: 横坐标
转移条件: 纵坐标
交叉数据: 新的状态

如图:
(我的图是横/纵坐标反了)

状态动作表:

key-value表:
key:是状态.
value:是触发的处理(动作)函数.

{
‘状态1’:callback1,
‘状态2’:callback2
}

实践一次

/**
 * @description 测试状态表
 *
 * 状态
 * 事件
 *
 */
const STA_START = 0
const STA_U1 = 1
const STA_U2 = 2
const STA_L1 = 3
const STA_END = 99
const STA_ERROR = 100

// 事件 ,条件 condition
const COND = {
  upper: 0,
  lower: 1,
  end: 9,
}
let state_transition_table = [
    //事件: upper,lower,end
    [STA_U1, STA_L1, STA_END], //START
    [STA_U2, STA_L1, STA_END], //U1
    [STA_U2, STA_L1, STA_END], //U2
    [STA_U1, STA_L1, STA_END], //L1
  ]
  // 转移到u1
function do_u1() {
  console.log('u1')
}

function do_u2() {
  console.log('u2')
}

function do_l1() {
    console.log('l1')
  }
  /**
   * 状态动作表
   * 根据状态查找:对应的动作回调
   */
let state_action_table = { // JS竟然会将标识符 转换为 字符串...,所以不能使用 STA_U1:do_u1
    1: do_u1,
    2: do_u2,
    3: do_l1,
  }
  // 当前状态
let m_statu = STA_START;

/**
 * @description {获取状态迁移}
 * @param {条件} condition
 * @param {当前状态} now_statu
 * @returns {新状态}
 */
function get_state_transition(condition, now_statu = m_statu) {
    return state_transition_table[now_statu][condition];
  }
  /**
   * 执行迁移状态,会修改现在状态,并执行迁移函数
   * @param {迁移到的状态} statu
   */
function move_status(statu) {
  console.log(state_action_table)
  let callback = state_action_table[statu];
  m_statu = statu; //改状态 ... ...
  if (callback == undefined) {
    throw ('无处理函数')
  }
  callback();
}



while (true) {
  //继续获取下一个条件
  //getNextCondition()
  let new_statu = get_state_transition(COND.lower); // 获取下一个状态
  console.log(new_statu)
  if (new_statu == STA_END) {
    break;
  }
  move_status(new_statu); // 迁移状态并触执行相关动作

}

重读状态机

状态机的核心是两个表:

  1. 通过 状态迁移表state_transition_table,容易实现:
    function(条件/*新字符etc.*/,现态/*当前判断是大小写*/) = 新态;
  2. 根据新状态做判断是否是错误状态,是否是终止状态.做跳出处理.
  3. 如果新状态没有问题,通过 状态动作表state_action_table,可以实现不同的状态下触发不同的行为.
    可以是个空行为,比如打印先态.

真正的跳出是在第二步,这样就避免了:

  • 状态动作表中做跳出处理.

分成两张表的好处在于不混乱.
2019年9月25日

你可能感兴趣的:(实践)