Golang中如何按顺序评估表达式中的每个必需变量

Golang中如何按顺序评估表达式中的每个必需变量 我目前正在使用 Go 语言包 github.com/Knetic/govaluate 来评估数据库中的公式。以下是我在数据库中使用的公式示例:

expression, err := govaluate.NewEvaluableExpression("totalCost / .85"); // 用于计算加价

parameters := make(map[string]interface{})
parameters["cost"] = 9.99;
parameters["qty"] = 2;
parameters["markup"] = nil // totalCost / .85 (上面的公式)
parameters["handlingCharge"] = nil // if(cost > 100, 25, cost * 0.1).
parameters["totalCost"] = nil // (cost * qty) + handlingCharge.

result, err := expression.Evaluate(parameters);

handlingChargetotalCost 本身也是公式。除了循环引用之外,公式包含其他公式字段没有限制。可以将其想象成 Excel 公式引用另一个公式字段。

与其编写 x 个 for 循环来检查每个必需的变量字段是否还包含另一个变量字段,我如何才能有效地处理每个必需的公式?

// 表达式映射
var expressionsMap = make(map[string]*govaluate.EvaluableExpression)
var requiredVariables = make(map[string][]string)

// 对于每个表字段
for publicID, formula := range fieldsMap {

	// 将表达式放入映射
	if formula != "" {
		expression, err := govaluate.NewEvaluableExpressionWithFunctions(formula, functions)
		if err != nil {
			log.Println(err)
			return nil, errInternalServerError
		}

		// 将表达式保存到表达式映射
		expressionsMap[publicID] = expression

        // 将所需变量保存到所需变量的映射中
		requiredVariables[publicID] = unique(expression.Vars())
	}
}

对于这个简单的例子,requiredVariables 将是:

requiredVariables['handlingCharge'] = ['cost']
requiredVariables['markup'] = ['totalCost']
requiredVariables['totalCost'] = ['cost', 'qty', 'handlingCharge']

总的来说,markup 需要 totalCost,而 totalCost 需要 handlingCharge。我如何以编程方式计算这些公式?感谢您的帮助。


更多关于Golang中如何按顺序评估表达式中的每个必需变量的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

你是想问如何追踪表达式的“叶子”节点,还是如何识别它们?也就是说,你是已经知道每个表达式的直接需求(这些需求本身可能是带有自己参数的表达式),还是试图弄清楚这些需求是什么?如果是后者,我认为你必须检查返回的表达式对象中的方法或字段,看看它是否包含你需要的内容。如果是前者,我认为你需要遍历表达式的依赖图,但如果表达式是不可变的,并且通过值或引用(只要不是通过名称)相互引用,那么你可以在编译表达式时完成这个操作:

var flattenedParams = make(map[string][]string)

func flattenParams(name string, directParams ...string) []string {
    if ps, ok := flattenedParams[name]; ok {
        return ps
    }
    out := make([]string, 0, 2*cap(directParams)) // 容量有点随意。
    for _, param := range directParams {
    deps:
        for _, dep := range flattenParams(param) {
            for _, o := range out {
                if o == dep {
                    continue deps
                }
            }
            out = append(out, dep)
        }
    }
    flattenedParams[name] = out
    return out
}

我可能遗漏了什么,但我希望这个思路是合理的:只要你首先定义了依赖关系,这个函数应该能够将一个表达式扁平化为它所依赖的所有参数。

更多关于Golang中如何按顺序评估表达式中的每个必需变量的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


要按顺序评估表达式中的每个必需变量,可以使用拓扑排序来处理依赖关系。以下是一个实现示例:

package main

import (
	"fmt"
	"log"

	"github.com/Knetic/govaluate"
)

func evaluateFormulas(expressionsMap map[string]*govaluate.EvaluableExpression,
	requiredVariables map[string][]string,
	initialParams map[string]interface{}) (map[string]interface{}, error) {

	// 复制初始参数
	params := make(map[string]interface{})
	for k, v := range initialParams {
		params[k] = v
	}

	// 跟踪已评估的表达式
	evaluated := make(map[string]bool)
	for k := range initialParams {
		evaluated[k] = true
	}

	// 拓扑排序评估
	for {
		progress := false

		for exprName, expr := range expressionsMap {
			if evaluated[exprName] {
				continue
			}

			// 检查所有依赖是否已评估
			allDepsEvaluated := true
			for _, dep := range requiredVariables[exprName] {
				if _, exists := params[dep]; !exists {
					allDepsEvaluated = false
					break
				}
			}

			if allDepsEvaluated {
				// 评估表达式
				result, err := expr.Evaluate(params)
				if err != nil {
					return nil, fmt.Errorf("failed to evaluate %s: %v", exprName, err)
				}
				params[exprName] = result
				evaluated[exprName] = true
				progress = true
			}
		}

		// 如果没有进展,检查是否所有表达式都已评估
		if !progress {
			// 检查是否有未评估的表达式
			remaining := []string{}
			for exprName := range expressionsMap {
				if !evaluated[exprName] {
					remaining = append(remaining, exprName)
				}
			}
			if len(remaining) > 0 {
				return nil, fmt.Errorf("circular dependency detected or missing dependencies: %v", remaining)
			}
			break
		}
	}

	return params, nil
}

func main() {
	// 示例数据
	functions := map[string]govaluate.ExpressionFunction{}
	
	// 创建表达式
	markupExpr, _ := govaluate.NewEvaluableExpressionWithFunctions("totalCost / .85", functions)
	handlingExpr, _ := govaluate.NewEvaluableExpressionWithFunctions("cost > 100 ? 25 : cost * 0.1", functions)
	totalCostExpr, _ := govaluate.NewEvaluableExpressionWithFunctions("(cost * qty) + handlingCharge", functions)

	expressionsMap := map[string]*govaluate.EvaluableExpression{
		"markup":        markupExpr,
		"handlingCharge": handlingExpr,
		"totalCost":     totalCostExpr,
	}

	requiredVariables := map[string][]string{
		"markup":        {"totalCost"},
		"handlingCharge": {"cost"},
		"totalCost":     {"cost", "qty", "handlingCharge"},
	}

	initialParams := map[string]interface{}{
		"cost": 9.99,
		"qty":  2.0,
	}

	// 评估所有公式
	results, err := evaluateFormulas(expressionsMap, requiredVariables, initialParams)
	if err != nil {
		log.Fatal(err)
	}

	// 输出结果
	fmt.Printf("cost: %.2f\n", results["cost"])
	fmt.Printf("qty: %.0f\n", results["qty"])
	fmt.Printf("handlingCharge: %.2f\n", results["handlingCharge"])
	fmt.Printf("totalCost: %.2f\n", results["totalCost"])
	fmt.Printf("markup: %.2f\n", results["markup"])
}

对于更复杂的依赖关系,可以使用基于深度优先搜索的拓扑排序:

func topologicalSort(requiredVariables map[string][]string) ([]string, error) {
	visited := make(map[string]int) // 0=未访问, 1=访问中, 2=已访问
	order := []string{}

	var dfs func(string) error
	dfs = func(node string) error {
		if visited[node] == 1 {
			return fmt.Errorf("circular dependency detected at %s", node)
		}
		if visited[node] == 2 {
			return nil
		}

		visited[node] = 1
		for _, dep := range requiredVariables[node] {
			if _, exists := requiredVariables[dep]; exists {
				if err := dfs(dep); err != nil {
					return err
				}
			}
		}
		visited[node] = 2
		order = append(order, node)
		return nil
	}

	for node := range requiredVariables {
		if visited[node] == 0 {
			if err := dfs(node); err != nil {
				return nil, err
			}
		}
	}

	return order, nil
}

func evaluateWithTopologicalSort(expressionsMap map[string]*govaluate.EvaluableExpression,
	requiredVariables map[string][]string,
	initialParams map[string]interface{}) (map[string]interface{}, error) {

	// 获取评估顺序
	order, err := topologicalSort(requiredVariables)
	if err != nil {
		return nil, err
	}

	// 合并参数
	params := make(map[string]interface{})
	for k, v := range initialParams {
		params[k] = v
	}

	// 按顺序评估
	for _, exprName := range order {
		expr, exists := expressionsMap[exprName]
		if !exists {
			continue
		}

		result, err := expr.Evaluate(params)
		if err != nil {
			return nil, fmt.Errorf("failed to evaluate %s: %v", exprName, err)
		}
		params[exprName] = result
	}

	return params, nil
}

这些实现会按依赖顺序评估所有公式,自动处理嵌套的公式引用,并检测循环依赖。

回到顶部