CI COV License: MIT

swift-math-parser

使用 Point•Freeswift-parsing 包 (v0.12.0) 构建的基础数学表达式解析器。 请参阅 API 文档 以获取开发者信息。

注意:v3.1.0 使用 swift-parsing v0.12,它需要 Xcode 14,理想情况下需要 Swift 5.8(请参阅他们的 更新日志 文档以获取更多详细信息)。 如果您需要使用旧版本,请改用标记为 3.0.1 的版本。

用法示例

let parser = MathParser()
let evaluator = parser.parse("4 × sin(t × π) + 2 × sin(t × π)")
evaluator.eval("t", value: 0.0) // => 0.0
evaluator.eval("t", value: 0.25) // => 4.2426406871192848
evaluator.eval("t", value: 0.5) // => 6
evaluator.eval("t", value: 1.0) // => 0

如果解析器无法完全解析表达式,它将返回 nil。 或者,您可以调用 parseResult 来获取一个 Swift Result 枚举,当解析失败时,它将具有一个 MathParserError 值。 这将包含来自 swift-parsing 库的解析失败的描述。

let evaluator = parser.parseResult("4 × sin(t × π")
print(evaluator)
failure(error: unexpected input
 --> input:1:8
1 | 4 × sin(t × π
  |        ^ expected end of input)

默认情况下,表达式解析器和求值器处理以下符号和函数

您可以通过提供自己的映射函数来引用其他符号或变量和函数。 有两个地方可以做到这一点

如果在 eval 调用期间某个符号或函数不存在,则最终结果将为 NaN。 如果在解析期间解析了某个符号,它将被替换为该符号的值。 否则,它将在未来的 eval 调用期间被解析。 函数调用也是如此 -- 如果在解析期间已知该函数并且所有参数都具有已知值,那么它将被替换为该函数的结果。 否则,函数调用将在 eval 调用期间进行。

您可以从 Evaluator.unresolved 属性中获取未解析的符号名称。 它返回三个集合,分别用于未解析的变量、一元函数和二元函数名称。 您也可以使用 evalResult 来尝试求值,但也可以获得求值失败的描述。

自定义符号

以下示例提供了一个自定义一元函数,该函数返回接收到的值的两倍。 还有一个名为 foo 的自定义变量,它保存常量 123.4

let myVariables = ["foo": 123.4]
let myFuncs: [String:(Double)->Double] = ["twice": {$0 + $0}]
let parser = MathParser(variables: myVariables.producer, unaryFunctions: myFuncs.producer)
let evaluator = parser.parse("power(twice(foo))")

# Expression parsed and `twice(foo)` resolved to `246.8` but `power` is still unknown
evaluator?.value // => nan
evaluator?.unresolved.unaryFunctions // => ['power']'
# Give evaluator way to resolve `power(246.8)`
let myEvalFuncs: [String:(Double)->Double] = ["power": {$0 * $0}]
evaluator?.eval(unaryFunctions: myEvalFuncs.producer) // => 60910.240000000005

您可以传递字典本身,而不是传递闭包来访问符号字典

let parser = MathParser(variableDict: myVariables, unaryFunctionDict: myFuncs)
evaluator?.eval(unaryFunctionDict: myEvalFuncs) // => 60910.240000000005

优先级

通常的数学运算遵循传统的优先级层次结构:乘法和除法运算发生在加法和减法之前,因此 1 + 2 * 3 - 4 / 5 + 6 的计算结果与 1 + (2 * 3) - (4 / 5) + 6 相同。 还有三个额外的运算符,一个用于求幂 (^) ,它的优先级高于前一个,所以 2 * 3 ^ 4 + 52 * (3 ^ 4) + 5 相同。 它也是右结合的,所以 2 ^ 3 ^ 4 被计算为 2 ^ (3 ^ 4) 而不是 (2 ^ 3) ^ 4

还有两个运算的优先级甚至高于指数运算

请注意,负数的阶乘未定义,因此不能组合取反和阶乘。 换句话说,解析 -3! 返回 nil。 此外,阶乘仅对数字的整数部分进行运算,因此 12.3! 将被解析,但结果值与 12! 相同。 实际上,阶乘始终作为 floor(x)!!(floor(x)) 运行。

隐式乘法

此解析器的最初目标之一是能够或多或少地按原样接受 Wolfram Alpha 数学表达式 -- 例如定义 https://www.wolframalpha.com/input/?i=Sawsbuck+Winter+Form%E2%80%90like+curve -- 无需任何编辑。 这是来自上面链接的文本表示的开始

x(t) = ((-2/9 sin(11/7 - 4 t) + 78/11 sin(t + 11/7) + 2/7 sin(2 t + 8/5) ...

跳过赋值,我们可以很容易地看到,当没有明确的数学运算符时,表示包括项之间的隐式乘法(例如 -2/9 x sin(11/7 - 4 x t))。 解析器支持这种运算,可以通过在创建新的 MathParser 实例时设置 enableImpliedMultiplication 来启用它(默认为 false)。 请注意,启用后,诸如 2^3 2^4 之类的表达式将被视为有效表达式,解析为 2^3 * 2^4 = 128,而 4sin(t(pi)) 将变为 4 * sin(t * pi)

您可以在 TestWolfram 测试用例中看到整个 Wolfram 示例。

这是来自本 README 文件开头的原始示例表达式,其中使用了隐式乘法(所有乘法符号都已删除)

let parser = MathParser(enableImpliedMultiplication: true)
let evaluator = parser.parse("4sin(t π) + 2sin(t π)")
evaluator.eval("t", value: 0.0) // => 0.0
evaluator.eval("t", value: 0.25) // => 4.2426406871192848
evaluator.eval("t", value: 0.5) // => 6
evaluator.eval("t", value: 1.0) // => 0

请注意,启用隐式乘法后,如果您在 "-" 运算符之间不使用空格,则可能会遇到奇怪的解析

但是,对于 "+",一切都很好

不幸的是,当不使用空格来表示意图时,无法处理隐式乘法、减法和取反之间的这种歧义。

符号拆分

当隐式乘法模式处于活动状态并且在其相应的映射中找不到变量或 1-参数(一元)函数的名称时,令牌求值例程将尝试通过将名称拆分为两个或多个片段来解析它们,这些片段都解析为已知的变量和/或函数。 例如,使用来自 MathParser 的默认变量映射和一元函数映射

如您所见,这可能会导致变量名和函数的错误解析,但仅当初始名称查找失败时才使用此行为,并且当符号名称被空格分隔时,永远不会执行此行为。 但是,如果您犯了一个错误并且忘记提供自定义变量或函数的定义,它可能会提供一个值而不是一个错误。 例如,考虑计算 tabs(-3),其中 t 是设置为 1.2 的自定义变量,而 tabs 是一个自定义函数,但未在自定义一元函数映射中提供

如果隐式乘法未处于活动状态,则求值器将正确报告问题 -- 要么返回 NaN,要么返回描述缺少函数的 Result.failure

脚注

  1. 精确到 20! -- 更大的数字是近似值

  2. 多余的,因为已经有 ^ 运算符。