表达式解析与求值 #
表达式可以通过多种方式进行解析和求值
- 使用函数
math.evaluate(expr [,scope])。 - 使用函数
math.compile(expr)。 - 使用函数
math.parse(expr)。 - 通过创建一个 解析器,
math.parser(),该解析器包含一个evaluate方法,并在内存中维护一个作用域,其中包含已分配的变量。
求值 #
Math.js 提供了一个名为 math.evaluate 的函数来求值表达式。语法
math.evaluate(expr)
math.evaluate(expr, scope)
math.evaluate([expr1, expr2, expr3, ...])
math.evaluate([expr1, expr2, expr3, ...], scope)
函数 evaluate 接受单个表达式或表达式数组作为第一个参数,并且有一个可选的第二个参数,其中包含一个带有变量和函数的 scope。作用域可以是常规的 JavaScript Map(推荐)、一个普通的 JavaScript object,或者任何实现了 Map 接口(具有 get、set、keys 和 has 方法)的自定义类。作用域将用于解析符号,以及写入已分配的变量和函数。
当使用 Object 作为作用域时,mathjs 会将其内部包装成 ObjectWrappingMap 接口,因为内部函数只能使用 Map 接口。对于自定义函数,例如 f(x) = x^2,作用域将被包装成 PartitionedMap。该 PartitionedMap 会从临时映射中读取和写入函数变量(例如此示例中的 x),并从原始作用域中读取和写入其他变量。原始作用域永远不会被复制,仅在需要时进行包装。
以下代码演示了如何求值表达式。
// evaluate expressions
math.evaluate('sqrt(3^2 + 4^2)') // 5
math.evaluate('sqrt(-4)') // 2i
math.evaluate('2 inch to cm') // 5.08 cm
math.evaluate('cos(45 deg)') // 0.7071067811865476
// provide a scope
let scope = {
a: 3,
b: 4
}
math.evaluate('a * b', scope) // 12
math.evaluate('c = 2.3 + 4.5', scope) // 6.8
scope.c // 6.8
编译 #
Math.js 包含一个名为 math.compile 的函数,该函数将表达式编译成 JavaScript 代码。这是先 解析 再编译表达式的快捷方式。语法是
math.compile(expr)
math.compile([expr1, expr2, expr3, ...])
函数 compile 接受单个表达式或表达式数组作为参数。函数 compile 返回一个对象,其中包含一个 evaluate([scope]) 函数,可以执行该函数来根据(可选的)作用域求值表达式。
const code = math.compile(expr) // compile an expression
const result = code.evaluate([scope]) // evaluate the code with an optional scope
表达式只需要编译一次,之后就可以重复地针对不同的作用域来求值表达式。可选的作用域用于解析符号以及写入已分配的变量或函数。参数 scope 可以是常规的 Object,或 Map。
示例用法
// parse an expression into a node, and evaluate the node
const code1 = math.compile('sqrt(3^2 + 4^2)')
code1.evaluate() // 5
解析 #
Math.js 包含一个名为 math.parse 的函数,用于将表达式解析成 表达式树。语法是
math.parse(expr)
math.parse([expr1, expr2, expr3, ...])
函数 parse 接受单个表达式或表达式数组作为参数。函数 parse 返回树的根节点,该节点可以被依次编译和求值。
const node = math.parse(expr) // parse expression into a node tree
const code = node.compile() // compile the node tree
const result = code.evaluate([scope]) // evaluate the code with an optional scope
节点 API 详细描述在 表达式树 页面。
表达式只需要解析和编译一次,之后就可以重复地求值表达式。求值时,可以提供一个可选的作用域,用于解析符号以及写入已分配的变量或函数。参数 scope 是一个常规的 Object 或 Map。
示例用法
// parse an expression into a node, and evaluate the node
const node1 = math.parse('sqrt(3^2 + 4^2)')
const code1 = node1.compile()
code1.evaluate() // 5
// provide a scope
const node2 = math.parse('x^a')
const code2 = node2.compile()
let scope = {
x: 3,
a: 2
}
code2.evaluate(scope) // 9
// change a value in the scope and re-evaluate the node
scope.a = 3
code2.evaluate(scope) // 27
解析后的表达式可以使用 node.toString() 导出为文本,并可以使用 node.toTex() 导出为 LaTeX。LaTeX 导出可用于使用 MathJax 等库在浏览器中美化打印表达式。示例用法
// parse an expression
const node = math.parse('sqrt(x/x+1)')
node.toString() // returns 'sqrt((x / x) + 1)'
node.toTex() // returns '\sqrt{ {\frac{x}{x} }+{1} }'
解析器 #
除了静态函数 math.evaluate 和 math.parse 之外,math.js 还包含一个解析器,其中包含 evaluate 和 parse 函数,该解析器会自动在内存中维护一个作用域,其中包含已分配的变量。解析器还包含一些方便的函数,用于从内存中获取、设置和删除变量。
可以通过以下方式创建解析器:
const parser = math.parser()
解析器包含以下函数:
clear()完全清空解析器的作用域。evaluate(expr)求值表达式。返回表达式的结果。get(name)从解析器的作用域中检索变量或函数。getAll()检索一个包含解析器作用域中所有已定义变量的对象。getAllAsMap()检索一个包含解析器作用域中所有已定义变量的 Map。remove(name)从解析器的作用域中删除变量或函数。set(name, value)在解析器的作用域中设置变量或函数。
以下代码展示了如何创建和使用解析器。
// create a parser
const parser = math.parser()
// evaluate expressions
parser.evaluate('sqrt(3^2 + 4^2)') // 5
parser.evaluate('sqrt(-4)') // 2i
parser.evaluate('2 inch to cm') // 5.08 cm
parser.evaluate('cos(45 deg)') // 0.7071067811865476
// define variables and functions
parser.evaluate('x = 7 / 2') // 3.5
parser.evaluate('x + 3') // 6.5
parser.evaluate('f(x, y) = x^y') // f(x, y)
parser.evaluate('f(2, 3)') // 8
// get and set variables and functions
const x = parser.get('x') // x = 3.5
const f = parser.get('f') // function
const g = f(3, 3) // g = 27
parser.set('h', 500)
parser.evaluate('h / 2') // 250
parser.set('hello', function (name) {
return 'hello, ' + name + '!'
})
parser.evaluate('hello("user")') // "hello, user!"
// clear defined functions and variables
parser.clear()
作用域 #
作用域是一个数据结构,用于存储和查找表达式定义和使用的变量及函数。
它通过调用 math.evaluate 或 simplify 传递给 mathjs。
为了方便使用,它可以是一个普通的 JavaScript 对象;为了安全起见,它可以是一个普通的 Map;为了灵活性,它可以是任何具有 Map 所具有的 get/set/has/keys 方法的对象。
为了能够收集 mathjs 脚本和表达式的定义,在修改传递给 mathjs 的对象时会格外小心。
evaluate 会在表达式使用黑名单符号时失败,从而防止 mathjs 表达式逃逸到 JavaScript。这通过访问作用域来强制执行。
为了减少对黑名单的依赖,作用域也可以是 Map,这允许 mathjs 表达式定义任何名称的变量和函数。
更多信息,请参阅 自定义作用域示例。
序列化 #
所有 mathjs 数据类型都可以序列化。因此,包含变量的作用域也可以安全地序列化。然而,在表达式解析器中,可以定义函数,例如:
f(x) = x^2
这种自定义函数本身无法序列化,因为它可能绑定到作用域中的其他变量。
一个 Parser 可以安全地序列化所有通过表达式解析器求值的变量和函数。
const parser = math.parser()
// evaluate some expressions
parser.evaluate('w = 2')
parser.evaluate('f(x) = x^w')
parser.evaluate('c = f(3)') // 9
// serialize the parser with its state
const str = JSON.stringify(parser)
// deserialize the parser again
const parser2 = JSON.parse(str, math.reviver)
parser.evaluate('f(4)') // 16