使用 Point•Free 的 swift-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)
默认情况下,表达式解析器和求值器处理以下符号和函数
+
)、减法 (-
)、乘法 (*
)、除法 (/
) 和 指数运算 (^
)!
) 1pi
(π
) 和 e
sin
, asin
, cos
, acos
, tan
, atan
, sec
, csc
, ctn
sinh
, asinh
, cosh
, acosh
, tanh
, atanh
log10
, ln
(loge
), log2
, exp
ceil
, floor
, round
, sqrt
(√
), cbrt
(立方根), abs
, 和 sgn
atan2
, hypot
, pow
2×
用于乘法,÷
用于除法(请参见上面的示例中使用 ×
)您可以通过提供自己的映射函数来引用其他符号或变量和函数。 有两个地方可以做到这一点
MathParser.init
Evaluator.eval
如果在 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 + 5
与 2 * (3 ^ 4) + 5
相同。 它也是右结合的,所以 2 ^ 3 ^ 4
被计算为 2 ^ (3 ^ 4)
而不是 (2 ^ 3) ^ 4
。
还有两个运算的优先级甚至高于指数运算
-
) -- -3.4
!
) -- 12!
请注意,负数的阶乘未定义,因此不能组合取反和阶乘。 换句话说,解析 -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
请注意,启用隐式乘法后,如果您在 "-" 运算符之间不使用空格,则可能会遇到奇怪的解析
2-3
=> -62 -3
-> -62 - 3
=> -1但是,对于 "+",一切都很好
2+3
=> 52 +3
-> 52 + 3
=> 5不幸的是,当不使用空格来表示意图时,无法处理隐式乘法、减法和取反之间的这种歧义。
当隐式乘法模式处于活动状态并且在其相应的映射中找不到变量或 1-参数(一元)函数的名称时,令牌求值例程将尝试通过将名称拆分为两个或多个片段来解析它们,这些片段都解析为已知的变量和/或函数。 例如,使用来自 MathParser
的默认变量映射和一元函数映射
pie
=> pi * e
esin(2π)
=> e * sin(2 * pi)
eeesgn(-1)
=> e * e * e * -1
如您所见,这可能会导致变量名和函数的错误解析,但仅当初始名称查找失败时才使用此行为,并且当符号名称被空格分隔时,永远不会执行此行为。 但是,如果您犯了一个错误并且忘记提供自定义变量或函数的定义,它可能会提供一个值而不是一个错误。 例如,考虑计算 tabs(-3)
,其中 t
是设置为 1.2
的自定义变量,而 tabs
是一个自定义函数,但未在自定义一元函数映射中提供
tabs(-3)
=> 1.2 * abs(-3)
=> 3.6
如果隐式乘法未处于活动状态,则求值器将正确报告问题 -- 要么返回 NaN,要么返回描述缺少函数的 Result.failure
。