自定义 #
除了解析和求值表达式外,表达式解析器还支持许多功能来定制表达式的处理和求值以及表达式的输出。
本页内容
函数转换 #
可以通过编写函数的转换来预处理函数参数和后处理函数的返回值。转换是包装待转换的函数或完全替换函数的函数。
例如,math.js 的函数使用零基矩阵索引(如编程语言中常见),但表达式解析器使用一基索引。为了实现这一点,所有处理索引的函数都有一个转换,它将输入从一基更改为零基,并将输出(和错误消息)从零基转换为一基。
// using plain JavaScript, indices are zero-based:
const a = [[1, 2], [3, 4]] // a 2x2 matrix
math.subset(a, math.index(0, 1)) // returns 2
// using the expression parser, indices are transformed to one-based:
const a = [[1, 2], [3, 4]] // a 2x2 matrix
let scope = {
a: a
}
math.evaluate('subset(a, index(1, 2))', scope) // returns 2
要为函数创建转换,必须将转换函数作为属性 transform 附加到该函数上
import { create, all } from 'mathjs'
const math = create(all)
// create a function
function addIt(a, b) {
return a + b
}
// attach a transform function to the function addIt
addIt.transform = function (a, b) {
console.log('input: a=' + a + ', b=' + b)
// we can manipulate input here before executing addIt
const res = addIt(a, b)
console.log('result: ' + res)
// we can manipulate result here before returning
return res
}
// import the function into math.js
math.import({
addIt: addIt
})
// use the function via the expression parser
console.log('Using expression parser:')
console.log('2+4=' + math.evaluate('addIt(2, 4)'))
// This will output:
//
// input: a=2, b=4
// result: 6
// 2+4=6
// when used via plain JavaScript, the transform is not invoked
console.log('')
console.log('Using plain JavaScript:')
console.log('2+4=' + math.addIt(2, 4))
// This will output:
//
// 6
具有转换的函数必须在 math 命名空间中导入,因为它们需要在编译时进行处理。在求值时通过作用域传递时不支持它们。
自定义参数解析 #
math.js 的表达式解析器支持让函数自行解析和求值参数,而不是用已求值的参数调用它们。例如,当创建类似 plot(f(x), x) 或 integrate(f(x), x, start, end) 的函数时,其中一些参数需要以特殊方式处理,这会很有用。在这些情况下,表达式 f(x) 将由函数重复求值,而 x 不会被求值,而是用于指定遍历函数 f(x) 的变量。
具有属性 rawArgs 且值为 true 的函数会由表达式解析器特殊处理:它们将以未求值的参数被调用,允许函数以自定义方式处理参数。原始函数调用方式如下:
rawFunction(args: Node[], math: Object, scope: Map)
其中
args是一个包含已解析参数节点的数组。math是表达式编译所依据的 math 命名空间。scope是一个Map接口,其中包含通过evaluate(scope)传递的作用域中定义的变量。传递的作用域始终是Map接口,通常使用PartitionedMap来分隔局部函数变量(如自定义函数f(x) = rawFunction(x) ^ 2中的x)与作用域变量。请注意,PartitionedMap可以递归链接到另一个PartitionedMap。
原始函数必须在 math 命名空间中导入,因为它们需要在编译时进行处理。在求值时通过作用域传递时不支持它们。
一个简单的例子
function myFunction(args, math, scope) {
// get string representation of the arguments
const str = args.map(function (arg) {
return arg.toString()
})
// evaluate the arguments
const res = args.map(function (arg) {
return arg.compile().evaluate(scope)
})
return 'arguments: ' + str.join(',') + ', evaluated: ' + res.join(',')
}
// mark the function as "rawArgs", so it will be called with unevaluated arguments
myFunction.rawArgs = true
// import the new function in the math namespace
math.import({
myFunction: myFunction
})
// use the function
math.evaluate('myFunction(2 + 3, sqrt(4))')
// returns 'arguments: 2 + 3, sqrt(4), evaluated: 5, 2'
自定义 LaTeX 处理程序 #
您可以在导入自定义函数之前为其附加 toTex 属性来定义其 LaTeX 输出。此 toTex 属性可以是下一节“自定义 LaTeX 和字符串转换”中描述的格式的处理程序,也可以是类似于 ES6 模板的模板字符串。
模板语法 #
${name}:将被替换为函数的名称${args}:将被替换为函数参数的逗号分隔列表。${args[0]}:将被替换为函数的第一个参数$$:将被替换为$
示例 #
const customFunctions = {
plus: function (a, b) {
return a + b
},
minus: function (a, b) {
return a - b
},
binom: function (n, k) {
return 1
}
}
customFunctions.plus.toTex = '${args[0]}+${args[1]}' //template string
customFunctions.binom.toTex = '\\mathrm{${name}}\\left(${args}\\right)' //template string
customFunctions.minus.toTex = function (node, options) { //handler function
return node.args[0].toTex(options) + node.name + node.args[1].toTex(options)
}
math.import(customFunctions)
math.parse('plus(1,2)').toTex() // '1+2'
math.parse('binom(1,2)').toTex() // '\\mathrm{binom}\\left(1,2\\right)'
math.parse('minus(1,2)').toTex() // '1minus2'
自定义 HTML、LaTeX 和字符串输出 #
所有表达式节点都有一个 toTex 和 toString 方法,分别用于以 HTML 或 LaTeX 格式输出表达式,或作为普通文本输出。函数 toHTML、toTex 和 toString 接受一个 options 参数来定制输出。此对象的格式如下:
{
parenthesis: 'keep', // parenthesis option
handler: someHandler, // handler to change the output
implicit: 'hide' // how to treat implicit multiplication
}
括号 #
parenthesis 选项改变输出中括号的使用方式。有三种可用选项:
keep:保留输入中的括号并按原样显示。这是默认设置。auto:仅显示必要的括号。Mathjs 尝试尽可能多地去除括号。all:显示节点树结构给出的所有括号。这使得输出的优先级无歧义。
有两种传递回调函数的方式
- 传递一个将函数名映射到回调函数的对象。这些回调函数将用于名称相同的函数的 FunctionNodes。
- 将函数传递给
toTex。然后将使用此函数处理每个节点。
const expression = math.parse('(1+1+1)')
expression.toString() // (1 + 1 + 1)
expression.toString({parenthesis: 'keep'}) // (1 + 1 + 1)
expression.toString({parenthesis: 'auto'}) // 1 + 1 + 1
expression.toString({parenthesis: 'all'}) // (1 + 1) + 1
处理程序 #
您可以为表达式的 toTex 和 toString 函数附加自己的自定义处理程序,以覆盖内部行为。这对于为自己的自定义函数提供 LaTeX/字符串输出特别有用。这可以通过两种方式完成:
- 传递一个将函数名映射到回调函数的对象。这些回调函数将用于包含具有该名称的函数的 FunctionNodes。
- 直接传递一个回调函数。此回调函数将为每个节点运行,因此您可以替换任何内容的输出。
回调函数具有以下形式:
function callback (node, options) {
...
}
其中 options 是传递给 toHTML/toTex/toString 的对象。不要忘记将其传递给子节点,node 是对当前节点的引用。
如果回调函数不返回任何内容,则使用标准输出。如果回调函数返回一个字符串,则使用该字符串。
虽然以下示例使用 toTex,但它对 toString 和 toHTML 的工作方式相同。
选项 1 的示例 #
const customFunctions = {
binomial: function (n, k) {
//calculate n choose k
// (do some stuff)
return result
}
}
const customLaTeX = {
'binomial': function (node, options) { //provide toTex for your own custom function
return '\\binom{' + node.args[0].toTex(options) + '}{' + node.args[1].toTex(options) + '}'
},
'factorial': function (node, options) { //override toTex for builtin functions
return 'factorial\\left(' + node.args[0] + '\\right)'
}
}
您可以通过将自定义 toTex 函数传递给 toTex 来简单地使用它们。
math.import(customFunctions)
const expression = math.parse('binomial(factorial(2),1)')
const latex = expression.toTex({handler: customLaTeX})
// latex now contains "\binom{factorial\\left(2\\right)}{1}"
选项 2 的示例:#
function customLaTeX(node, options) {
if ((node.type === 'OperatorNode') && (node.fn === 'add')) {
//don't forget to pass the options to the toTex functions
return node.args[0].toTex(options) + ' plus ' + node.args[1].toTex(options)
}
else if (node.type === 'ConstantNode') {
if (node.value === 0) {
return '\\mbox{zero}'
}
else if (node.value === 1) {
return '\\mbox{one}'
}
else if (node.value === 2) {
return '\\mbox{two}'
}
else {
return node.value
}
}
}
const expression = math.parse('1+2')
const latex = expression.toTex({handler: customLaTeX})
// latex now contains '\mbox{one} plus \mbox{two}'
结合自定义函数的另一个示例
const customFunctions = {
binomial: function (n, k) {
//calculate n choose k
// (do some stuff)
return result
}
}
function customLaTeX(node, options) {
if ((node.type === 'FunctionNode') && (node.name === 'binomial')) {
return '\\binom{' + node.args[0].toTex(options) + '}{' + node.args[1].toTex(options) + '}'
}
}
math.import(customFunctions)
const expression = math.parse('binomial(2,1)')
const latex = expression.toTex({handler: customLaTeX})
// latex now contains "\binom{2}{1}"
隐式乘法 #
您可以更改将隐式乘法转换为字符串或 LaTeX 的方式。两个选项是 hide,用于不显示隐式乘法的乘法运算符;show,用于显示它。
示例
const node = math.parse('2a')
node.toString() // '2 a'
node.toString({implicit: 'hide'}) // '2 a'
node.toString({implicit: 'show'}) // '2 * a'
node.toTex() // '2~ a'
node.toTex({implicit: 'hide'}) // '2~ a'
node.toTex({implicit: 'show'}) // '2\\cdot a'
自定义支持的字符 #
可以自定义符号和数字中允许的字符。 parse 函数公开了以下测试函数:
math.parse.isAlpha(c, cPrev, cNext)math.parse.isWhitespace(c, nestingLevel)math.parse.isDecimalMark(c, cNext)math.parse.isDigitDot(c)math.parse.isDigit(c)
这些函数的确切签名和实现可以在 解析器源代码 中查找。允许的字母字符在此处描述:常量和变量。
例如,电话字符 ☎ 默认不支持。通过替换 isAlpha 函数可以启用它。
const isAlphaOriginal = math.parse.isAlpha
math.parse.isAlpha = function (c, cPrev, cNext) {
return isAlphaOriginal(c, cPrev, cNext) || (c === '\u260E')
}
// now we can use the \u260E (phone) character in expressions
const result = math.evaluate('\u260Efoo', {'\u260Efoo': 42}) // returns 42
console.log(result)