手写一个解析器

2023-10-27

作者:jolamjiang,腾讯 WXG 前端开发工程师

前言

最近工作中有一些同学在做一些效能工具的时候遇到需要写一门领域相关语言(DSL)及其解析器的场景,笔者恰好有相关的经验向大家指一下北。

首先请问一下大家有没有想过这个功能怎么做?

点击播放视频

本文将围绕如何实现类似于 Excel 中 =C1+C2+"123" 这样子的表达式的功能这一例子,在不需要编译原理的相关知识的前提下,用写正则表达式作为类比,借助一个工具库,讲述实现一个领域相关语言的解析器的一般步骤,让你能够快速实现一个解析器。同时,文章最后将给出一个将类似 MySQL 里面 Where 表达式转化成 MongoDB 查询的例子丰富这里的应用。

正则及其限制

在日常工作中,经常会遇到模式匹配的问题,例如你能需要从 0755-8771032 这样的电话号码格式中提取出区号和区号和电话号码,然后保存下来;可能需要判断 test@domain.com.cn 这样的邮箱地址是否合法;又可能你需要实现类似于 Excel 里面表达的功能,例如用户输入 =C1+C2+"123",你需要把 C1 的内容和 C2 的内容和字符串 "123" 拼接起来。

我们一般的做法是使用正则表达来做这个事情,以 Python 为例,系统提供的 API 我们可以看做分三步走:

import re
pattern = "^([0-9])-([0-9]+)$" // 1. write regex string of phone number
prog = re.compile(pattern) // 2. compile regex to a matcher
result = prog.match(string) // 3. match string and handle results

目前为止正则表达式都看起来都没问题,以 =C1+C2+"123" 这个需求为例,你可能会觉得我们按照运算符(+-* 等)分割一下然后再计算就行了,但是考虑下面三个 case:

  1. 运算符有优先级,例如 =C1+C2*C3=C1*C2+C3,需要先计算 * 再计算 +

  2. 字符串里面有运算符,例如 =C1+C2+"=C1+C2"

  3. 运算有左右括号匹配来改变运算优先级,例如 =(C1+C2)*C3

这个时候光使用正则表达式就比较棘手了。

通用做法

业界通用的做法是先定义这个领域相关的语法,将这个语法形式化描述(就像写正则表达式),然后根据这语法实现一个 Parser 将代码转成抽象语法树(AST),再解析和运行这颗抽象语法树。

上述整个过程听起来就比较复杂,事实上要从 0 开始实现一个 Parser 还是比较费时的,那么有没有工具能够让我们可以像写正则一样生成我们的 Parser,进而产生一颗抽象语法树方便我们处理呢?答案是有的,例如 C 语言有 Bison 框架,JS 上选择就更多了,你可以选择 JisonparsimmonPEG.jsNearley 等,本文则基于使用人数较多的 Nearley 框架。

如何写一个解析器

与使用写正则类似,使用 Nearley 等 Parser 产生器的过程,也是分三步走。

1. 用 BNF 来表示你的 DSL 语法

BNF 的全称是 Backus–Naur form,是一种表示上下文无关语法的表示方式,Nearley 的语法基于 BNF 的扩展 EBNF(Extended Backus–Naur form),下面是笔者写的关于这个 Excel 中的表达式的 Nearley 语法文件(为了便于理解,这里只实现了运算符的优先级,没有实现左右括号):

grammar.ne

@builtin "number.ne"
@builtin "whitespace.ne"
@builtin "string.ne"

@{%
    function buildAssignmentExpression(d) {
        return {
            type: "AssignmentExpression",
            op: d[2],
            left: d[0],
            right: d[4]
        };
    }
%}

# Assignment
Exp -> Assignment {% id %}
    | Value {% id %}


Assignment -> "=" _ Expression {% d => {
    return {
        type: "Assignment",
        value: d[2]
    }
} %}

# Expression
Expression -> AddSubExpression {% id %}

# Expression for Add Sub
AddSubExpression -> AddSubExpression _ "+" _ MulDivExpression {% d => buildAssignmentExpression(d) %}
    | AddSubExpression _ "-" _ MulDivExpression {% d => buildAssignmentExpression(d) %}
    | MulDivExpression {% id %}

# Expression for Mul Div
MulDivExpression -> Identifier _ "*" _ MulDivExpression {% d => buildAssignmentExpression(d) %}
    | Identifier _ "/" _ MulDivExpression {% d => buildAssignmentExpression(d) %}

    | Value _ "*" _ MulDivExpression {% d => buildAssignmentExpression(d) %}
    | Value _ "/" _ MulDivExpression {% d => buildAssignmentExpression(d) %}
    
    | Value {% id %}
    | Identifier {% id %}

# Cell Identifier
Identifier -> [A-Z]:+ [0-9]:+ {%
        function(d) {
            return {
                'type': "AssignmentIdentifier",
                'column': d[0].join(""),
                'line': d[1].join("")
            }
        }
    %}

# Values
Value -> _value {%
        function(d) {
            return {
                'type': "Value",
                'value': d[0]
            };
        }
    %}

_value -> int {% id %}
    | unsigned_decimal {% id %}
    | decimal {% id %}
    | dqstring {% id %}
    | sqstring {% id %}
    | btstring {% id %}

1.1 引入语法模块

我们一步步来分析这个文件的内容,首先是头部这段代码:

@builtin "number.ne"
@builtin "whitespace.ne"
@builtin "string.ne"

Nearley 预定义了一些常用的语法,这段代码的意思是引入了 Nearley 预定义的数字语法,空格语法和字符串语法。引入完了之后,生成的 Parser 就可以识别例如 "123" 这样的字符串、123 这样的数字。

Nearley 内置的语法模块可以在这里查看。

1.2 Helper 变量和函数

接着是这段代码:

@{%
    function buildExpression(d) {
        return {
            type: "Expression",
            op: d[2],
            left: d[0],
            right: d[4]
        };
    }
%}

在 Nearley 里面,{% raw %}@{% ... %}{% endraw %} 里面的内容相当于在全局声明了一些变量,这些变量可以在产生式的 Post Processor 里用到。至于什么叫产生式紧接接下来会介绍到。

1.3 书写产生式

我们拿其中一个比较复杂的产生式来讲解一下:

MulDivExpression -> Identifier _ "*" _ MulDivExpression {% d => buildAssignmentExpression(d) %}
    | Identifier _ "/" _ MulDivExpression {% d => buildAssignmentExpression(d) %}

    | Value _ "*" _ MulDivExpression {% d => buildAssignmentExpression(d) %}
    | Value _ "/" _ MulDivExpression {% d => buildAssignmentExpression(d) %}
    
    | Value {% id %}
    | Identifier {% id %}

Nearley 里面 | 这个运算符其实是个语法糖,上面的产生式其实可以表示成多条产生式:

MulDivExpression -> Identifier _ "*" _ MulDivExpression {% d => buildAssignmentExpression(d) %}
MulDivExpression -> Identifier _ "/" _ MulDivExpression {% d => buildAssignmentExpression(d) %}
MulDivExpression -> Value _ "*" _ MulDivExpression {% d => buildAssignmentExpression(d) %}
MulDivExpression -> Value _ "/" _ MulDivExpression {% d => buildAssignmentExpression(d) %}
MulDivExpression -> Value {% id %}
MulDivExpression -> Identifier {% id %}

在介绍每一个产生式之前,我们先介绍两个概念:

  1. 符号:它代表代码某一部分,例如 if 语句 if (...) { ... } 整一块可以看做是一个符号,字符串 "123" 可以看做是一个符号,符号是一个递归的概念,符号可以包含其他符号。例如 if (...) { a = "123" } 这个 "if" 符号包含了字符串符号 "123"

  2. 终结符:当一个符号不包含其他符号了,那么它就是终结符。例如字符串符号 "123" 中的 1 这是个终结符,因为它不能细分其它符号了。

具体到每一条产生式,可分三个部分:

  1. -> 的左边是非终结符符号,它代表父级的概念,它可以包含多个符号或者终结符。

  2. -> 右边内容是左边符号的展开表达式,它代表符号能够如何被展开,它可以包含多个符号或终结符。

  3. 最后部分是 Nearley 的 Post Processor,它会在应用完这条产生式后执行,它也是一段 JS 代码,它可以使用我们之前定义的 Helper 变量和函数。它的运行结果将会作为整条产生式的运行结果。

至此如何书写 BNF 就介绍完了,你可以已经发现了,正则表达式也可以用 BNF 来表示,事实上正则也是上下文无关的问题,自然也就可以用 BNF 来表示。

2. 生成 Parser

生成 Parser 会用到我们之前介绍到的 Nearley 框架,首先我们将上面给出的 BNF 语法定义保存到 grammar.ne 文件里。

  1. 我们先运行 npm install --save nearley 来为项目安装 Nearley 依赖,然后运行 npm install -g nearley 来安装 Nearley 相关命令的全局依赖。

  2. 运行 nearleyc grammar.ne -o grammar.js 生成 Parser 相关文件 grammar.js

  3. 运行下面的代码即可对 DSL 代码进行解析了:

const nearley = require("nearley");
const grammar = require("./grammar.js");

// Create a Parser object from our grammar.
const parser = new nearley.Parser(nearley.Grammar.fromCompiled(grammar));

// Parse something!
parser.feed("=C1+C2*C3");

// parser.results is an array of possible parsings.
console.log(parser.results);

3. 解析 Parser 结果

步骤 2 完成了之后,我们就可以得到 DSL 代码对应的抽象语法树,所谓的抽象语法树其实就是一个 JSON 对象,例如 =C1+D1*E1 这个代码对应的 JSON 对象的结构就如下图所示

那么下一步就是怎么解析这个树状结构的对象,然后得到它对应的结果。这里我们用最简单的自循环解析器来对这棵树进行求值。自循环解析器的原理很简单,我们将得到的 AST 树进行从底往上地求值,整个过程是对树进行深度遍历完成的。

求值之前,我们先对数的非叶子节点定义一些原子操作:

  1. Identifier: 在 Excel 中拿到对应的行列将其作为 Identifier 节点的值返回。

  2. Expression: 将 Expression 节点的左右运算参与者根据运算符进行运算,例如某个 Expression 的运算符是 *,则将 Expression 的左边和右边乘起来。

  3. Assignment: 将 Assignment 下的 Expression 的值作为语句的返回。

有了上述原子操作之后,就可以开始我们的求值了,最开始深度遍历到 D1E1 对应的 Identifier 之后,我们根据上述的原子操作对 Identifier 的值进行替换,假设 D1E1 对应的值分别是 1112,则第一次递归求值后,树就变成了:

下一层的递归则对第二层的 IdentifierExpression 节点进行求值,根据上述的原子操作,假设 C1 对应的值是 33,树就变成了:

以此类推,我们就可以得到这棵树的最终值 33 + 132 = 165

下面给出实现递归的代码和对应的 AST,对于某些同学来说,可能直接看代码更容易理解:

ast.json

{
  "type": "Assignment",
  "value": {
    "type": "AssignmentExpression",
    "op": "+",
    "left": {
      "type": "AssignmentIdentifier",
      "column": "C",
      "line": "1"
    },
    "right": {
      "type": "AssignmentExpression",
      "op": "*",
      "left": {
        "type": "AssignmentIdentifier",
        "column": "D",
        "line": "1"
      },
      "right": {
        "type": "AssignmentIdentifier",
        "column": "E",
        "line": "1"
      }
    }
  }
}

eval.js

function evalAst(exp, rows) {
  if (exp.type == "Assignment") {
    return evalAst(exp.value, rows);
  }
  if (exp.type == "Value") {
    return exp.value;
  }
  if (exp.type == "AssignmentIdentifier") {
    return rows[exp.line][exp.column];
  }
  if (exp.type == "AssignmentExpression") {
    switch(exp.op) {
      case "+":
        return evalAst(exp.left, rows) + evalAst(exp.right, rows);
      case "-":
        return evalAst(exp.left, rows) - evalAst(exp.right, rows);
      case "*":
        return evalAst(exp.left, rows) * evalAst(exp.right, rows);
      case "/":
        return evalAst(exp.left, rows) / evalAst(exp.right, rows);
      default:
        throw new Error("invalid operator");
        break;
    }
  }
  throw new Error("invalid expression type");
}

最后 DEMO 可以在这里查看:

  1. 代码:https://stackblitz.com/edit/react-excel-example

  2. DEMO:https://react-excel-example.stackblitz.io/

另外一个例子

为了加深理解,这里给出另外一个需求,将 MySQL 类似于 where 转换成云函数里面的 where 筛选的需求,给出 BNF 语法和 Eval JS 代码:

grammar.ne

@builtin "number.ne"
@builtin "whitespace.ne"
@builtin "string.ne"

@{%
    function buildExpression(d) {
        return {
            type: "Expression",
            op: d[2],
            left: d[0],
            right: d[4]
        };
    }
%}

# exp
Exp -> Binop {% id %}

Binop -> ExpOr {% id %}

ExpOr -> ExpOr __ "or" __ ExpAnd {% d => buildExpression(d) %}
  | ExpAnd {% id %}

ExpAnd -> ExpAnd __ "and" __ ExpComparison {% d => buildExpression(d) %}
  | ExpComparison {% id %}

ExpComparison ->
    Name _ "<"  _ Value {% d => buildExpression(d) %}
  | Name _ ">"  _ Value {% d => buildExpression(d) %}
  | Name _ "<=" _ Value {% d => buildExpression(d) %}
  | Name _ ">=" _ Value {% d => buildExpression(d) %}
  | Name _ "~=" _ Value {% d => buildExpression(d) %}
  | Name _ "==" _ Value {% d => buildExpression(d) %}

# variables
Name -> _name {%
    function(d) {
      return {
        'type': "Identifier",
        'name': d[0]
      };
    }
  %}

_name -> [a-zA-Z_] {% id %}
  | _name [\w_] {% function(d) {return d[0] + d[1]; } %}

# values
Value -> _value {%
    function(d) {
      return {
        'type': "Value",
        'value': d[0]
      };
    }
  %}

_value -> int {% id %}
  | unsigned_decimal {% id %}
  | decimal {% id %}
  | dqstring {% id %}
  | sqstring {% id %}
  | btstring {% id %}

eval.ts

type Expression = {
    type: "Expression",
    op: "or" | "and" | "<" | ">" | ">=" | "<=" | "~=" | "==",
    left: Expression | Identifier,
    right: Expression | Value
}

type Identifier = {
    type: "Identifier",
    name: string
}

type Value = {
    type: "Value",
    value: number | string
}

export default function __eval(ast: Expression, _: any) {

    function evalExpression(expression: Expression) {
        switch (expression.op) {
            case "or":
                return _.or([
                    evalExpression((expression as any).left),
                    evalExpression((expression as any).right),
                ]);
                break;
            case "and":
                return _.and([
                    evalExpression((expression as any).left),
                    evalExpression((expression as any).right),
                ]);
                break;
            case "<":
                return {
                    [(expression as any).left.name]: _.lt((expression as any).right.value)
                }
                break;
            case ">":
                return {
                    [(expression as any).left.name]: _.gt((expression as any).right.value)
                }
                break;
            case ">=":
                return {
                    [(expression as any).left.name]: _.gte((expression as any).right.value)
                }
                break;
            case "<=":
                return {
                    [(expression as any).left.name]: _.lte((expression as any).right.value)
                }
                break;
            case "~=":
                return {
                    [(expression as any).left.name]: _.neq((expression as any).right.value)
                }
                break;
            case "==":
                return {
                    [(expression as any).left.name]: _.eq((expression as any).right.value)
                }
                break;
            default:
                throw new Error("invalid expression");
                break;
        }
    }

    return evalExpression(ast)
}

总结

到此为止读者应该具备写自己的 DSL 和解析器的能力了,学会写自己的 DSL 和解析器其实还有别的好处,例如它可以让你更好地理解我们平常说的配置系统是什么,其实配置也是代码,试想 if (config(...)) { ... } else { ... } 中的 config(...) 其实就是你的配置系统需要承载的内容,又例如你要实现一拖拽生成 UI 的工具,其实你就是在用拖拽生成了一颗 AST 树,然后在你的产品里实现了一个解析 AST 的解析器来渲染结果。同时反过来,你可以思考你的配置系统可以实现一些什么样的能力,它的上限就是能达到与写代码一样的功能,不过笔者不推荐这么做,因为业界一些方案例如 Blockly 或者流程图类似的方案来表示逻辑其实体验都不是很好,同时这些系统对使用者的素质要求不亚于要求他们直接写代码。

另外微信支付行业缴费、微信支付支付分行业招聘前端和后台工程师,在这里你可以:

  1. 接触到国民民生级应用产品,海量的业务挑战与海量的满足感。

  2. 接触到优秀的、不同专长的同事,以火箭般的速度成长。

  3. 丰富的个人发展空间。

是不是心动了,赶快点击下列链接了解详情吧:

  1. 28601-微信支付行业缴费开发工程师(深圳)

  2. 28601-微信支付分行业Web前端工程师

  3. 28601-微信支付分行业后台开发工程师(深圳)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

手写一个解析器 的相关文章

  • 数据库作业11:SQL练习7 - GRANT/ REVOKE / AUDIT

    授权 授予与收回 GRANT语句向用户授予对数据的操作权限 REVOKE语句向用户收回对数据的操作权限 1 GRANT 语句格式为 GRANT lt 权限 gt lt 权限 gt ON lt 对象类型 gt lt 对象名 gt lt 对象类
  • mediapipe KNN 基于mediapipe和KNN的引体向上计数/深蹲计数/俯卧撑计数【mediapipe】

    原文链接 https blog csdn net m0 57110410 article details 125569971 Mediapipe KNN引体向上计数 深蹲计数 俯卧撑计数 引言 功能 说明 步骤 训练样本 获取归一化的 la
  • 4个数据整理Excel小技巧,省下你80%的工作时间

    各位小伙伴 不知道你觉得工作中哪项任务是最烦的呢 那么今天 将几个特别实用的整理报表数据Excel小技巧带给大家 让我们把复杂的问题简单化 来吧 好好学习 天天向上 更加进步吧 1 输入证件号码 在录入个人身份信息的时候 身份证号有时候是经
  • 又一款 AI 应用开源了,让你的绘画作品动起来!

    这是 进击的Coder 的第 824 篇技术分享 作者 小 G 来源 GitHubDaily 阅读本文大概需要 4 分钟 2021 年的时候 Meta 前身是 Facebook 团队发布了一款非常有趣的 AI 工具 叫 Animated D
  • Excel根据出生日期判断生肖,Leo老师来教你!

    在工作学习中 我们经常会遇到Excel根据出生日期判断生肖这样的问题 列夫托尔斯泰说过 人生不是一种享乐 而是一桩十分沉重的工作 因此 面对Excel根据出生日期判断生肖我们应该有努力探索的精神 成功的人千方百计 失败的人千难万险 对于这个
  • python虚拟环境安装使用

    conda下操作 1 查看已经安装的虚拟环境 conda env list 2 创建 conda create n your env name 3 进入虚拟环境 conda activate your env name 4 退出虚拟环境 c
  • 09.二叉树

    09 二叉树 1 树型结构 1 1概念 树是一种非线性的数据结构 它是由n n gt 0 个有限结点组成一个具有层次关系的集合 把它叫做树是因为它看起来像一棵倒挂的树 也就是说它是根朝上 而叶朝下的 它具有以下的特点 有一个特殊的结点 称为
  • 最小二乘法与伪逆矩阵

    一 简介 最小二乘法是一种数学优化技术 通过最小化误差的平方和寻找数据的最佳函数匹配 利用最小二乘法可以简便地求得未知的数据 并是得这些求得的数据与实际数据之间误差的平方和最小 二 最小二乘法拟合直线的原理 1 假设存在n个坐标点 他们的坐
  • C语言的四种程序结构

    1 顺序结构 顺序结构的程序设计是最简单的 只要按照解决问题的顺序写出相应的语句就行 它的执行顺序是自上而下 依次执行 例如 a 3 b 5 现交换a b的值 这个问题就好像交换两个杯子水 这当然要用到第三个杯子 假如第三个杯子是c 那么正
  • anaconda python未激活_anaconda无法激活新建环境,提示没有那个文件或目录

    遇到的问题 最新在部署tensorflow 但是由于cpu型号比较老的原因 所以直接pip安装tensorflow会提示core dump 吐核 所以需要使用conda来建立一个新的环境 然后使用conda来安装tf即可解决吐核问题 但是有
  • 高效实现延迟消息功能

    高效实现延迟消息功能 高效延时消息 包含两个重要的数据结构 1 环形队列 例如可以创建一个包含3600个slot的环形队列 本质是个数组 2 任务集合 环上每一个slot是一个Set 同时 启动一个timer 这个timer每隔1s 在上述
  • Unity2D入门(七):物理材质、跳跃、基础UI

    一 物理材质 游戏角色在跳跃过程中如果正面碰撞到地形就会卡在上面 所以需要为其添加一种材质 在Assets窗口中 右键 gt 新建一个物理材质 不用做其他的调整 将其拖拽到Player Inspector窗口中的BoxCollider组件的
  • Python测试框架pytest(17)参数化parametrize

    目录 1 参数 2 装饰测试类 3 多个参数化装饰器 4 参数化 传入字典数据 5 标记参数化 6 解决unicode编码问题 pytest mark parametrize 允许在测试函数或类中定义多组参数和 fixtures 参数化场景
  • Stream流

    Stream流的常见生成方式 Stream流中间操作之filter Stream流的常见中间操作方法 Stream流终结操作之forEach count Stream流的收集操作 Stream流的常见生成方式 Stream流的使用 生成流
  • SpringBoot错误: 找不到或无法加载主类

    1 一般出现这种情况都是配置文件application properties出现的问题 2 可以尝试 maven clean install 以及rebuild project 3 删除项目里 idea文件 重新导入至IDEA编辑器 选择M
  • vs2010中臃肿的ipch和sdf文件

    使用VS2010建立C 解决方案时 会生成SolutionName sdf和一个叫做ipch的文件夹 这两个文件再加上 pch等文件使得工程变得非常的庞大 一个简单的程序都会占用几十M的硬盘容量 可惜毕竟硬盘还没有廉价到免费的地步 那么 该
  • 绘图系统二:多图绘制系统

    文章目录 坐标轴控件 坐标系控件 绘制多组数据 源代码 本文基于 从0开始实现一个三维绘图系统 坐标轴控件 三个坐标轴xyz从外观上看其实毫无区别 这种标签和输入框的组合十分常见 为了便于调用 最好实现一个类 tkinter只要继承Fram
  • MyBatis-Plus中的逻辑删除使用

    系列文章目录 Mybatis Plus SpringBoot结合运用 心态还需努力呀的博客 CSDN博客MyBaits Plus中 TableField和 TableId用法 心态还需努力呀的博客 CSDN博客 MyBatis Plus分页
  • 如何通过C语言自动生成MAC地址

    如何通过C语言自动生成MAC地址 最近在做虚拟机项目时 需要给创建的每一个虚拟机自动生成一个MAC地址 由于MAC地址为48位 而且格式是以 隔开的 所以下面我写了一个c程序 来自动生成MAC地址 MAC c include
  • solidity实现智能合约教程(5)-NFT拍卖合约

    文章目录 1 介绍 2 主要功能 3 代码示例 4 部署测试 猛戳订阅学习专栏 solidity系列合约源码 解析 1 介绍 拍卖作为历史悠久的交易方式 具有规范化 市场化的特点 在经济活动中扮演着重要角色 以其公开 公平 公正的价格发现功

随机推荐

  • unity动态加载(1)Resources加载方法

    在开发过程中我们很可能需要使用到动态加载 这样一方面可以节省性能 另一方面使我们的开发过程更加便捷 我之前写过一篇游戏中音效控制器 可以很方便的播放音效 就是用Resources 传送门 大家如果有兴趣可以参考 然后这篇博客实现以下使用Re
  • [推荐] (SqlServer)批量清理指定数据库中所有数据

    在实际应用中 当我们准备把一个项目移交至客户手中使用时 我们需要把库中所有表先前的测试数据清空 以给客户一个干净的数据库 如果涉及的表很多 要一一的清空 不仅花费时间 还容易出错以及漏删 在这儿我提供了一个方法 可快捷有效的清空指定数据库所
  • 杭电1005.找规律就好

    本题连接 点击打开链接 Number Sequence Time Limit 2000 1000 MS Java Others Memory Limit 65536 32768 K Java Others Total Submission
  • 【人工智能】Fisher 线性分类器的设计与实现(QDU)

    人工智能 Astar算法求解8数码问题 QDU 人工智能 利用 搜索的博弈树算法编写一字棋游戏 QDU 人工智能 Fisher 线性分类器的设计与实现 QDU 人工智能 感知器算法的设计实现 QDU 人工智能 SVM 分类器的设计与应用 Q
  • Invalid option 'latest' for /langversion; must be ISO-1, ISO-2, Default or an integer in range 1 to

    Easily Add PDF Word Excel Function to Your NET Apps You may be thinking that C 7 features are already supported with Vis
  • 【C语言】浮点型数据为什么不能直接比较

    由于浮点型的精度是有限的 经过运算就可能存在舍入误差 比如 x y y x 所以如果要比较浮点型数值 最好要先定义一个极小值MIN作为允许误差 1 浮点型与0比较 define MIN 0 0000000001 double temp if
  • 1个星期,教你快速上手Unity ASE-【UI流动】

    目录 前言回顾 效果图 节点预览 步骤 前言回顾 不熟悉节点属性的可以点击传送门预览 传送门 1个星期 教你快速上手Unity ASE 预览 传送门 1个星期 教你快速上手Unity ASE 遮罩 传送门 1个星期 教你快速上手Unity
  • Qt中ui文件的使用

    用designer设计的 ui文件可以通过uic工具转换为 h文件 在编译时也会自动生成这样一个ui h文件 有了这个 h文件就可以直接按照纯C 的方式对其中的类进行调用 ui文件的使用就是利用默认工具uic自动产生一个类 然后用该类的se
  • 【数据结构与算法】<==>二叉树下

    目录 堆的应用 1 堆排序 1 建堆 2 向下调整的时间复杂度 3 向上调整建堆的时间复杂度 二叉树链式结构的实现 遍历操作 其他操作 堆的应用 1 堆排序 堆排序即利用堆的思想来进行排序 总共分为两个步骤 1 建堆 升序 建大堆 序 建大
  • 小程序隐私保护的常见问题汇总 小程序隐私配置解决

    涉及调用用户个人信息相关接口的 每一个小程序均需补充相应用户隐私保护指引 1 设置隐私保护的时候 clientip is not registered 返回的错误信息 errCode 61004 errMsg access clientip
  • UE4,UE5虚幻引擎,怎么在蓝图中获取FPS帧速率,显示在UMG(UI)上

    前言 在UE中可以使用命令行比如stat fps显示帧率 但只是显示在界面 假设我们要在蓝图中获取FPS帧率 并且显示在我们创建的UMG控件蓝图 这种stat fps命令行的形式就不行了 因为它只会固定显示在右上角的位置 1 在Tick中获
  • js data 日期初始化

    创建一个日期对象 var objDate new Date arguments list 参数形式有 以下 种 1 new Date month dd yyyy hh mm ss 2 new Date month dd yyyy 3 new
  • 小白数学建模模型入门(一)

    数学建模模型入门 一 1 层次分析法 AHP 针对于难以完全定量的复杂系统做出决策的模型和方法 如图 判断好目标层 准测层 可理解为考虑元素 方案层 不把所有因素放在一起比较 而是两两互相比较 两因素比较的标度由参考论文或其他途径得出 得到
  • 【C++】类的默认成员函数(上)

    文章目录 类的默认成员函数 1构造函数 1 1构造函数特征 1 2编译器自动生成的构造函数 1 3编译器默认的构造函数 1 4C 11特征 2析构函数 2 1特征 2 2编译器默认的析构函数 3拷贝构造函数 3 1特征 3 2编译器默认生成
  • ssh渗透与hydra爆破(简明不啰嗦)

    适合新手上路 MSF与hydra两种方式渗透22端口 后进行远程连接 如有不足请各位见谅 此次实验仅供参考 切勿做违法犯罪 出事一切与本人无关后果自负 希望大家早日成为白帽子 渗透机 kali 靶机 192 168 75 128 1 扫描局
  • vim配置全攻略(2)——vim的简单配置

    这篇文章主要讲的是vim的简单配置 相对于vim内置的一些快捷键和功能 vim的客制化才是vim的灵魂 也是vim存活31年仍被热衷的原因 如果你还不了解vim的基本操作和体系 我建议你看一下我上一篇文章 vim配置全攻略 1 vim的基本
  • 【ARM】rk3399挂载nfs报错

    挂载nfs报错mount mnt bad option for several filesystems e g nfs cifs you might need a sbi 实验1 无效 sudo apt get install cifs u
  • 基于Spring Cloud实现日志管理模块

    简介 无论在什么系统中 日志管理模块都属于十分重要的部分 接下来会通过注解 AOP MQ的方式实现一个简易的日志管理系统 思路 注解 标记需要记录日志的方法 AOP 通过AOP增强代码 利用后置 异常通知的方式获取相关日志信息 最后使用MQ
  • Python+Django+Nginx+Uwsgi(史上最全步骤)

    步骤 Python安装 第一步安装Python 很多购买的服务器linux系统中自带python2和python3 我是直接使用自带的python3 5版本的 如果系统中没有 则自己安装 如下 1 下载 wget https www pyt
  • 手写一个解析器

    作者 jolamjiang 腾讯 WXG 前端开发工程师 前言 最近工作中有一些同学在做一些效能工具的时候遇到需要写一门领域相关语言 DSL 及其解析器的场景 笔者恰好有相关的经验向大家指一下北 首先请问一下大家有没有想过这个功能怎么做 点