探索FSM (有限状态机)应用

news2024/11/27 5:23:49

有限状态机(FSM) 是计算机科学中的一种数学模型,可用于表示和控制系统的行为。它由一组状态以及定义在这些状态上的转换函数组成。FSM 被广泛用于计算机程序中的状态机制。

有限状态机(FSM)应用场景

  • 在各种自动化系统的应用: 例如交通信号灯、地铁站的旋转闸门、银行自动取款机等。通过对状态和转换函数的定义,可以实现对系统行为的精确控制。

    交通信号灯状态流转图

    file

    地铁站的旋转闸门状态流转图

    file

    银行自动取款机状态流转图

    file

  • 在编程领域的应用: 例如在编写编译器和解释器时,可以使用有限状态机(FSM) 来处理词法分析。例如:JSON.Parse

  • 在Notion中应用: 可以使用 有限状态机(FSM) 的相关概念来构建各种工作流程,例如状态转换图、状态转换表等。

  • 在web中应用: 我们熟悉的 Promise 也是一个状态机,具有三个状态:pending、resolved。rejected。

    Promise状态流转图

    file

    登录功能流转图

    file

类似这样的状态机的例子数不胜数,甚至于,人也是一种极其复杂的状态机,给定一种刺激或多种刺激组合,也会触发人从某种状态过渡到另一种状态。只不过复杂程度极高,以至于现代科学完全无法解密这种状态机。

有限状态机(FSM)实现原理

具体来说,FSM由以下几部分组成:

  • 初始状态:系统的初始状态。
  • 状态集合:表示系统可能处于的各种状态。
  • 转移函数:定义系统在不同状态之间的转移条件和结果。
  • 终止状态:系统在某个状态下可以停止计算。

有限状态机(FSM) 的实现基于状态转移图状态转移图 是一个有向图,它表示有限状态机(FSM) 中状态之间的转移关系。在状态转移图中,每个状态表示系统的某种状态,每个转移表示系统从一个状态转移到另一个状态的条件和结果。

实现简易的有限状态机(FSM)

实现步骤

  • 当状态机开始执行时,它会自动进入初始化状态(initial state)。
  • 每个状态都可以定义,在进入(onEnter)或退出(onExit)该状态时发生的行为事件(actions),通常这些行为事件会携带副作用(side effect)。
  • 每个状态都可以定义触发转换(transition)的事件。
  • 转换定义了在退出一个状态并进入另一个状态时,状态机该如何处理这种事件。
  • 在状态转换发生时,可以定义可以触发的行为事件,从而一般用来表达其副作用。

状态转移图

file

function createMachine(stateMachineDefinition) {
  const machine = {
    value: stateMachineDefinition.initialState,
    performTransition(currentState, event) {
      const currentStateDefinition = stateMachineDefinition[currentState];
      const destinationTransition = currentStateDefinition.transitions[event];
      if (!destinationTransition) {
        return;
      }
      const destinationState = destinationTransition.target;
      const destinationStateDefinition =
        stateMachineDefinition[destinationState];

      destinationTransition.action();
      currentStateDefinition.actions.onExit();
      destinationStateDefinition.actions.onEnter();

      machine.value = destinationState;

      return machine.value;
    },
  };
  return machine;
}

const machine = createMachine({
  initialState: "off",
  off: {
    actions: {
      onEnter() {
        console.log("off: onEnter");
      },
      onExit() {
        console.log("off: onExit");
      },
    },
    transitions: {
      switch: {
        target: "on",
        action() {
          console.log('transition action for "switch" in "off" state');
        },
      },
    },
  },
  on: {
    actions: {
      onEnter() {
        console.log("on: onEnter");
      },
      onExit() {
        console.log("on: onExit");
      },
    },
    transitions: {
      switch: {
        target: "off",
        action() {
          console.log('transition action for "switch" in "on" state');
        },
      },
    },
  },
});

let state = machine.value;
console.log(`current state: ${state}`);
state = machine.performTransition(state, "switch");
console.log(`current state: ${state}`);
state = machine.performTransition(state, "switch");
console.log(`current state: ${state}`);

有限状态机(FSM)的 应用实现

在状态比较多的情况下,把状态、事件及 transitions 集中到一个状态机中,进行统一管理。这样不需要写太多的 if-else,或者 case 判断,如果增加状态和事件,也便于代码的维护和扩展。

文本解析器

实现思路

  • 确定状态和输入
    在编写 FSM 之前,我们需要确定我们的状态和输入。在这个例子中,我们将定义三个状态:起始状态、数字状态和字符串状态。我们还将定义四个输入:数字、字母、引号和空格。
  • 定义状态机类
    现在,我们可以编写代码来实现我们的 FSM 。我们需要定义一个状态机类,它将接受输入,并根据转移规则转换状态。该类应该包含以下属性:
    • currentState:当前状态。
    • states:状态列表。
    • transitions:转移列表。
      它还应该包含以下方法:
    • transition:该方法接受一个输入参数 input,根据当前状态以及输入参数,执行相应的状态转换。
  • 定义转移规则
    我们还需要定义状态之间的转移规则。为此,我们将使用转移列表,其中包含状态之间的映射和输入。转移规则应该考虑当前状态和输入,并根据它们确定下一个状态。如果当前状态和输入没有匹配的转移规则,则应该抛出一个异常。
  • 解析文本
    现在,我们可以使用状态机解析文本。我们需要将文本拆分为单词,并将每个单词作为输入提供给状态机。在处理完所有输入后,我们可以通过调用 getInputType 方法来获取解析的令牌。

示例代码

const STATES = {
  START: "start",
  NUMBER: "number",
  STRING: "string",
};

const INPUTS = {
  NUMBER: "number",
  LETTER: "letter",
  SPACE: "space",
  QUOTE: "quote",
};

const TRANSITIONS = [
  {
    currentState: STATES.START,
    input: INPUTS.NUMBER,
    nextState: STATES.NUMBER,
  },
  {
    currentState: STATES.START,
    input: INPUTS.LETTER,
    nextState: STATES.STRING,
  },
  { currentState: STATES.START, input: INPUTS.SPACE, nextState: STATES.START },
  { currentState: STATES.START, input: INPUTS.QUOTE, nextState: STATES.STRING },
  {
    currentState: STATES.NUMBER,
    input: INPUTS.NUMBER,
    nextState: STATES.NUMBER,
  },
  { currentState: STATES.NUMBER, input: INPUTS.SPACE, nextState: STATES.START },
  {
    currentState: STATES.STRING,
    input: INPUTS.LETTER,
    nextState: STATES.STRING,
  },
  { currentState: STATES.STRING, input: INPUTS.SPACE, nextState: STATES.START },
  { currentState: STATES.STRING, input: INPUTS.QUOTE, nextState: STATES.START },
];

class TextParse {
  constructor() {
    this.currentState = STATES.START;
    this.buffer = "";
    this.type;
  }

  performTransition(input) {
    const transition = TRANSITIONS.find(
      (t) => t.currentState === this.currentState && t.input === input.type
    );
    if (!transition)
      throw new Error(
        `Invalid input "${input.value}" for state "${this.currentState}"`
      );

    this.currentState = transition.nextState;

    if (this.currentState === STATES.START) {
      const token = this.buffer;
      const type = this.type;
      this.buffer = "";
      this.type = "";
      return {
        type,
        value: token,
      };
    } else {
      this.buffer += input.value;
      this.type = input.type;
    }
  }
}

function textParse(input) {
  const textParse = new TextParse();
  const tokens = [];

  for (let i = 0; i < input.length; i++) {
    const char = input[i];

    try {
      const token = textParse.performTransition({
        type: getInputType(char),
        value: char,
      });

      if (token) {
        tokens.push(token);
      }
    } catch (e) {
      console.error(e.message);
      return null;
    }
  }

    const lastToken = textParse.performTransition({ type: INPUTS.SPACE });

  if (lastToken) {
    tokens.push(lastToken);
  }

  return tokens;
}

function getInputType(char) {
  if (/[0-9]/.test(char)) {
    return INPUTS.NUMBER;
  } else if (/[a-zA-Z]/.test(char)) {
    return INPUTS.LETTER;
  } else if (/[\s\n\t\r]/.test(char)) {
    return INPUTS.SPACE;
  } else if (char === '"') {
    return INPUTS.QUOTE;
  } else {
    throw new Error(`Unknown input type for "${char}"`);
  }
}

// Example usage:
console.log(textParse('123 abc "def ghi" 456')); 
// [
//   { type: 'number', value: '123' },
//   { type: 'letter', value: 'abc' },
//   { type: 'letter', value: '"def' },
//   { type: 'letter', value: 'ghi' },
//   { type: '', value: '' },
//   { type: 'number', value: '456' }
// ]

示例代码

web 应用

使用 有限状态机(FSM) 结合 React 构建 web 应用,不局限于身份认证,登录,步骤表单,有蛮多 web 应用在
有限状态机(FSM)的实践 ,下面主要描述 从有限状态机(FSM)在服务端拉取数据的状态转移上的应用

  • 状态转移图

    file

  • 状态集(States)转换规则(Transitions)

const states = {
  INITIAL: "idle",
  LOADING: "loading",
  SUCCESS: "success",
  FAILURE: "failure",
};
const transitions = {
  [states.INITIAL]: {
    fetch: () => /* Returns states.LOADING */,
  },

  [states.LOADING]: {},

  [states.SUCCESS]: {
    reload: () => /* Returns states.LOADING */,
    clear: () => /* Returns states.INITIAL */,
  },

  [states.FAILURE]: {
    retry: () => /* Returns states.LOADING */,
    clear: () => /* Returns states.INITIAL */,
  },
}

示例代码

总结

结合前端应用的探索体现的不多,可以再作为第二篇内容去探讨,有兴趣的同学可以尝试一下 有限状态机(FSM) 在 web 上的应用探索,以及 Xstate库(FSM封装的功能性库) 的应用,以及跟 状态管理库 差异化的知识。在这里提醒一点,状态管理库 (Redux) 和 Xstate 并不是互斥的,Xstate 关注的是如何设计状态,状态管理库关注的是如何管理状态。事实上,状态机几乎可以与任何无主见的状态管理工具一起使用。我鼓励您探索各种方法,以确定最适合您、您的团队和您的应用程序的方法。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/867323.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LVGL学习笔记 29 - LED

目录 1. 设置颜色 2. 设置OFF颜色 3. 设置对比度 4. 改变状态 功能类似CheckBox&#xff0c;用一个方形或则圆形的控件显示开关状态。 lv_obj_t* led1 lv_led_create(lv_scr_act());lv_obj_t* led2 lv_led_create(lv_scr_act());lv_obj_align(led1, LV_ALIGN_CENTER, -80…

生产执行MES系统:提升企业灵活性和响应速度的关键利器

在竞争激烈的市场环境下&#xff0c;企业需要不断提高其灵活性和响应速度&#xff0c;以适应快速变化的需求和市场动态。生产执行MES&#xff08;Manufacturing Execution System&#xff09;系统作为信息技术的重要应用&#xff0c;为企业提供了强大的工具和平台&#xff0c;能…

redis 数据结构(一)

Redis 为什么那么快 redis是一种内存数据库&#xff0c;所有的操作都是在内存中进行的&#xff0c;还有一种重要原因是&#xff1a;它的数据结构的设计对数据进行增删查改操作很高效。 redis的数据结构是什么 redis数据结构是对redis键值对值的数据类型的底层的实现&#xff0c…

金蝶云星空与金蝶云星空对接集成采购入库查询连通采购入库新增(MW_写入测试)

金蝶云星空与金蝶云星空对接集成采购入库查询连通采购入库新增(MW_写入测试) 对接源平台:金蝶云星空 金蝶K/3Cloud在总结百万家客户管理最佳实践的基础上&#xff0c;提供了标准的管理模式&#xff1b;通过标准的业务架构&#xff1a;多会计准则、多币别、多地点、多组织、多税…

YOLOv5基础知识入门(3)— 目标检测相关知识点

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。YOLO算法发展历程和YOLOv5核心基础知识学习完成之后&#xff0c;接下来我们就需要学习目标检测相关知识了。为了让大家后面可以顺利地用YOLOv5进行目标检测实战&#xff0c;本节课就带领大家学习一下目标检测的基础知识点&…

运放电路笔记5-其它典型电路

电容的特性——“通交流&#xff0c;隔直流” 电容器可以用来对交流信号进行通路&#xff0c;同时隔离直流信号。这是因为电容器对交流信号具有低阻抗&#xff08;通过&#xff09;和对直流信号具有高阻抗&#xff08;阻断&#xff09;的特性。 在低频情况下&#xff0c;电容器…

Layui精简版,快速入门

目录 LayUI之入门 1.什么是layui 2.layui入门 3.自定义模块 4.用户登录 5.主页搭建 LayUI之动态树 main.jsp main.js LayUI之动态选项卡 1.选项卡 main.jsp main.js 2.用户登录 User.java UserDao.java UserAction.java R.java LayUI之用户管理 1.用户查询…

填补封闭社区一加ACE2V在151版本下安装KernelSU方式获取ROOT

背景需求,Android移动端软件太过流氓,随意驻留后台,其他root方案不满意 第一步,请将手机升级到你想稳定的版本 参考文档, 安装 | KernelSU https://kernelsu.org/zh_CN/guide/installation.html#patch-boot-image 免github下载地址 KernelSU https://mrzzoxo.lanzoub.com/b…

设计模式之工厂方法模式(FactoryMethod)

一、概述 定义一个用于创建对象的接口&#xff0c;让子类决定实例化哪一个类。FactoryMethod使一个类的实例化延迟到其子类。 二、适用性 1.当一个类不知道它所必须创建的对象的类的时候。 2.当一个类希望由它的子类来指定它所创建的对象的时候。 3.当类将创建对象的职责委…

[ubuntu]创建root权限的用户 该用户登录后自动切换为root用户

一、创建新用户 1、创建新用户 sudo useradd -r -m -s /bin/bash 用户名 # -r&#xff1a;建立系统账号 -m&#xff1a;自动建立用户的登入目录 -s&#xff1a;指定用户登入后所使用的shell2、手动为用户设置密码 passwd 用户名 二、为用户增加root权限 1、添加写权限 ch…

DIP: NAS(Neural Architecture Search)论文阅读与总结(双份快乐)

文章地址: NAS-DIP: Learning Deep Image Prior with Neural Architecture SearchNeural Architecture Search for Deep Image Prior 参考博客:https://zhuanlan.zhihu.com/p/599390720 文章目录 NAS-DIP: Learning Deep Image Prior with Neural Architecture Search1. 方法…

ChatGPT等人工智能编写文章的内容今后将成为常态

BuzzFeed股价上涨200%可能标志着“转向人工智能”媒体趋势的开始。 周四&#xff0c;一份内部备忘录被华尔街日报透露BuzzFeed正计划使用ChatGPT聊天机器人-风格文本合成技术来自OpenAI&#xff0c;用于创建个性化盘问和将来可能的其他内容。消息传出后&#xff0c;BuzzFeed的…

SpringBoot 3自带的 HTTP 客户端工具

原理 Spring的HTTP 服务接口是一个带有HttpExchange方法的 Java 接口&#xff0c;它支持的支持的注解类型有&#xff1a; HttpExchange&#xff1a;是用于指定 HTTP 端点的通用注释。在接口级别使用时&#xff0c;它适用于所有方法。GetExchange&#xff1a;为 HTTP GET请求指…

题解:ABC276E - Round Trip

题解&#xff1a;ABC276E - Round Trip 题目 链接&#xff1a;Atcoder。 链接&#xff1a;洛谷。 难度 算法难度&#xff1a;普及。 思维难度&#xff1a;提高。 调码难度&#xff1a;提高。 综合评价&#xff1a;困难。 算法 bfs。 思路 从起点周围四个点中任选两…

jenkins自动化构建保姆级教程(持续更新中)

1.安装 1.1版本说明 访问jenkins官网 https://www.jenkins.io/&#xff0c;进入到首页 点击【Download】按钮进入到jenkins下载界面 左侧显示的是最新的长期支持版本&#xff0c;右侧显示的是最新的可测试版本&#xff08;可能不稳定&#xff09;&#xff0c;建议使用最新的…

代码随想录算法学习心得 51 | 503、下一个更大的元素II 42、接雨水...

一、下一个更大元素II 链接&#xff1a;力扣 描述如下&#xff1a;给定一个循环数组 nums &#xff08; nums[nums.length - 1] 的下一个元素是 nums[0] &#xff09;&#xff0c;返回 nums 中每个元素的 下一个更大元素 。 数字 x 的 下一个更大的元素 是按数组遍历顺序&am…

2023年五款免费高效的在线客服系统大揭秘!

近年来&#xff0c;随着移动互联网的蓬勃发展&#xff0c;企业与消费者之间的互动方式正在迅速演变&#xff0c;从传统的PC端转向了更加便捷灵活的移动端。在这个变革的大背景下&#xff0c;为了满足日益增长的客户需求&#xff0c;企业对于提供优质客户服务的迫切需求也逐渐凸…

【雕爷学编程】Arduino动手做(09)---火焰传感器模块4

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

基于时态差分法的强化学习:Sarsa和Q-learning

时态差分法&#xff08;Temporal Difference, TD&#xff09;是一类在强化学习中广泛应用的算法&#xff0c;用于学习价值函数或策略。Sarsa和Q-learning都是基于时态差分法的重要算法&#xff0c;用于解决马尔可夫决策过程&#xff08;Markov Decision Process, MDP&#xff0…

LoadRunner(2)

一、Controller 1.1场景设计 1.通过VUG打开 施压机器&#xff1a;发起请求的角色(用户本地电脑) 被压机器&#xff1a;处理请求的角色(服务器) 2.直接双击Controller 场景设计&#xff1a;需要关注三个部分 第一部分&#xff1a; 第二部分&#xff1a; 2.1运行场景…