跳到主要内容

符号树变量计算器

符号树变量计算器(VariableCalculator)是一个基于抽象语法树(AST)的数学表达式计算工具,支持变量替换和公式缓存,特别适合需要重复计算的动态表达式场景。

基础用法

简单运算(不推荐)

// 不含变量的简单运算
Arim.variableCalculator.calculate("1 + 2 * 3") // 7.0
推荐

这种用法不如使用 固定算数表达式计算器,因为需要额外的解析和构建语法树开销。

如果需要数学函数(如 sin、log、sqrt 等),应使用 FixedCalculator,它在 6.2.3+ 版本支持丰富的数学函数。

预解析上下文(推荐)

val vc = Arim.variableCalculator

// 1. 预解析公式为上下文对象(推荐缓存此对象)
val context = vc.parseContext("a + b * ( level + 10 )")

// 2. 使用 Map 传递变量
val result1 = context.evaluate(mapOf(
"a" to 1.0,
"b" to 2.0,
"level" to 3.0
)) // 27.0

// 3. 使用可变参数传递变量
val result2 = context.evaluate(
"a" to 5.0,
"b" to 3.0,
"level" to 10.0
) // 65.0

// 4. 使用指数运算
val powerContext = vc.parseContext("base^exponent + 10")
val result3 = powerContext.evaluate(
"base" to 2.0,
"exponent" to 3.0
) // 18.0 (2^3 + 10)
最佳实践

缓存 NodeContext 对象,需要计算时直接传入变量即可。

核心概念

Node - 符号树节点

Node 是符号树的基本单元,使用组合模式设计:

节点类型说明示例
ValueNode常量值节点5.0, 3.14
DynamicNode变量节点level, health
AdditionNode加法节点a + b
SubtractionNode减法节点a - b
MultiplicationNode乘法节点a * b
DivisionNode除法节点a / b
ModulusNode取模节点a % b
PowerNode指数节点a^b

NodeContext - 解析上下文

NodeContext 封装了解析后的符号树,提供便捷的求值方法:

class NodeContext(var node: Node) {
fun evaluate(variable: Map<String, Double>): Double
fun evaluate(vararg variables: Pair<String, Double>): Double
}

方法签名

parseContext - 解析为上下文(推荐)

fun parseContext(expression: String): NodeContext

示例:

val context = Arim.variableCalculator.parseContext("a + b * c")

// 缓存 context,重复使用
val result1 = context.evaluate("a" to 1.0, "b" to 2.0, "c" to 3.0)
val result2 = context.evaluate("a" to 5.0, "b" to 4.0, "c" to 2.0)

实际应用示例

伤害计算公式

object DamageCalculator {
// 基础伤害计算
private val damageFormula = Arim.variableCalculator.parseContext(
"baseDamage * attackMultiplier * ( 1 + critRate ) - defense"
)

// 暴击伤害计算(使用指数运算)
private val critDamageFormula = Arim.variableCalculator.parseContext(
"baseDamage * critMultiplier^critStack"
)

fun calculateDamage(
baseDamage: Double,
attackMultiplier: Double,
critRate: Double,
defense: Double
): Double {
return damageFormula.evaluate(
"baseDamage" to baseDamage,
"attackMultiplier" to attackMultiplier,
"critRate" to critRate,
"defense" to defense
)
}

fun calculateCritDamage(
baseDamage: Double,
critMultiplier: Double,
critStack: Double
): Double {
return critDamageFormula.evaluate(
"baseDamage" to baseDamage,
"critMultiplier" to critMultiplier,
"critStack" to critStack
)
}
}

配置文件公式系统

rewards:
kill_boss:
gold_formula: "baseGold * bossLevel * ( 1 + playerLevel / 100 )"
level_up:
exp_formula: "baseExp * level^1.5"
daily_reward:
bonus_formula: "baseBonus * ( 1 + streak / 10 )^2"
object RewardSystem {
private val formulaCache = mutableMapOf<String, NodeContext>()

fun loadFormulas(config: Configuration) {
config.getKeys(false).forEach { key ->
// 加载金币公式
val goldFormula = config.getString("$key.gold_formula")
if (goldFormula != null) {
formulaCache["${key}_gold"] = Arim.variableCalculator.parseContext(goldFormula)
}

// 加载经验公式
val expFormula = config.getString("$key.exp_formula")
if (expFormula != null) {
formulaCache["${key}_exp"] = Arim.variableCalculator.parseContext(expFormula)
}

// 加载奖励公式
val bonusFormula = config.getString("$key.bonus_formula")
if (bonusFormula != null) {
formulaCache["${key}_bonus"] = Arim.variableCalculator.parseContext(bonusFormula)
}
}
}

fun calculateReward(rewardType: String, variables: Map<String, Double>): Double {
return formulaCache[rewardType]?.evaluate(variables) ?: 0.0
}

// 使用示例
fun getLevelUpExp(level: Int): Double {
return calculateReward("level_up_exp", mapOf(
"baseExp" to 100.0,
"level" to level.toDouble()
))
}
}

性能优化

缓存 NodeContext

// ✅ 推荐: 缓存 NodeContext
object Calculator {
private val formula = Arim.variableCalculator.parseContext("a + b * c")

fun calculate(a: Double, b: Double, c: Double): Double {
return formula.evaluate("a" to a, "b" to b, "c" to c)
}
}

// ❌ 不推荐: 每次都解析
fun calculate(a: Double, b: Double, c: Double): Double {
val formula = Arim.variableCalculator.parseContext("a + b * c")
return formula.evaluate("a" to a, "b" to b, "c" to c)
}

性能对比

操作耗时说明
首次解析~10-50μs构建符号树
缓存后求值~1-5μs树遍历求值

常见问题

如何缓存 NodeContext?

// 方案 1: 静态变量(推荐)
object Calculator {
private val formula = Arim.variableCalculator.parseContext("a + b")
}

// 方案 2: Map 缓存
val cache = mutableMapOf<String, NodeContext>()
cache["myFormula"] = Arim.variableCalculator.parseContext("a + b")

// 方案 3: 懒加载
object Calculator {
val damageFormula by lazy {
Arim.variableCalculator.parseContext("baseDamage * multiplier")
}
}

变量不存在时会发生什么?

如果变量在 Map 中不存在,会使用默认值 0.0

val context = Arim.variableCalculator.parseContext("a + b")
val result = context.evaluate("a" to 10.0) // b 不存在,使用 0.0
println(result) // 10.0

可以使用 Int 类型的变量吗?

不可以,变量值必须是 Double 类型:

// ✅ 正确
context.evaluate("level" to 10.toDouble())
context.evaluate("level" to 10.0)

指数运算的优先级是多少?

指数运算 ^ 的优先级最高(优先级 4),高于乘除法(优先级 3):

val context1 = Arim.variableCalculator.parseContext("a * b^c")
context1.evaluate("a" to 2.0, "b" to 3.0, "c" to 2.0) // 18.0 (2 * 9)

val context2 = Arim.variableCalculator.parseContext("(a + b)^c")
context2.evaluate("a" to 2.0, "b" to 3.0, "c" to 2.0) // 25.0 (5^2)

VariableCalculator 支持数学函数吗?

不支持。VariableCalculator 专注于变量计算,不支持数学函数(sin、log、sqrt 等)。

功能对比:

特性VariableCalculatorFixedCalculator
变量支持
数学函数✅(6.2.3+ 版本)
指数运算
推荐场景含变量的重复计算固定公式 + 数学函数

选择建议:

// 需要变量 → 使用 VariableCalculator
val context = Arim.variableCalculator.parseContext("baseDamage * level^1.5")
val damage = context.evaluate("baseDamage" to 10.0, "level" to 5.0)

// 需要数学函数 → 使用 FixedCalculator
val angle = Arim.fixedCalculator.evaluate("sin(30) + cos(60)")