本算法被应用在了 程序员学英语 项目中对缩小单词进行拆分的任务上. 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.youkuaiyun.com/younggift/article/details/35848677
https://yunjianfei.iteye.com/blog/2061790
有限状态机的三种实现方式:
- switch-case/if-else
最直观,庞大状态机难以维护. - 状态表
维护一个二维状态表.横坐标表示当前状态,
纵坐标表示输入,表中:
一个元素存储下一个状态和对应的操作. - State Pattern
状态模式
状态表法(推荐)
优点是:增加状态和事件都很方便维护.
查两张表:
- 状态转移表
用 现态 和 条件,以及哪张表 查询,获得下一个状态 为当前状态. - 状态动作表
用 当前状态 查询
获得函数并执行之.
状态转移表:是个数组:
现态: 横坐标
转移条件: 纵坐标
交叉数据: 新的状态
如图:
(我的图是横/纵坐标反了)
状态动作表:
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); // 迁移状态并触执行相关动作
}
重读状态机
状态机的核心是两个表:
- 通过 状态迁移表state_transition_table,容易实现:
function(条件/*新字符etc.*/,现态/*当前判断是大小写*/) = 新态;
- 根据新状态做判断是否是错误状态,是否是终止状态.做跳出处理.
- 如果新状态没有问题,通过 状态动作表state_action_table,可以实现不同的状态下触发不同的行为.
可以是个空行为,比如打印先态.
真正的跳出是在第二步,这样就避免了:
- 在状态动作表中做跳出处理.
分成两张表的好处在于不混乱.
2019年9月25日