golang动态决策树规则引擎插件库ddt的使用

golang动态决策树规则引擎插件库ddt的使用

DDT是一个允许基于一组定义规则构建自定义决策树的Golang库,可以通过编程方式或从JSON创建。

动态决策树

DDT允许在比较决策树的下一个可能分支之前,对输入添加预处理阶段。DDT中默认的预处理函数包括使用反射调用结构体方法(CallStructMethod)和获取结构体属性(GetStructAttribute)。

示例

创建用户树

使用结构体作为树的输入,并在比较前预处理数据。在这个例子中,我们使用了用户结构体的一些方法和属性。

用户树示例

package main

import (
	"fmt"
	"github.com/sgrodriguez/ddt"
	"github.com/sgrodriguez/ddt/compare"
	"github.com/sgrodriguez/ddt/function"
	"github.com/sgrodriguez/ddt/value"
)

type user struct {
	Age       int
	FirstName string
	LastName  string
}

func (u *user) UnderAge() bool {
	return u.Age < 18
}

func (u *user) FullName() string {
	return u.FirstName + " " + u.LastName
}

func main() {
	node6 := &ddt.Node{
		ID:             6,
		ParentID:       2,
		ValueToCompare: &value.Value{Type: value.Int, Value: 30},
		Result:         &value.Value{Type: value.String, Value: "node6"},
		Comparer:       &compare.Greater{},
	}
	node5 := &ddt.Node{
		ID:             5,
		ParentID:       2,
		ValueToCompare: &value.Value{Type: value.Int, Value: 30},
		Result:         &value.Value{Type: value.String, Value: "node5"},
		Comparer:       &compare.Lesser{Equal: true},
	}
	node3 := &ddt.Node{
		ID:             3,
		ParentID:       1,
		ValueToCompare: &value.Value{Type: value.String, Value: "SANTIAGO LUCIA"},
		Result:         &value.Value{Type: value.String, Value: "node3"},
		Comparer:       &compare.Equal{},
	}
	node4 := &ddt.Node{
		ID:             4,
		ParentID:       1,
		ValueToCompare: &value.Value{Type: value.String, Value: "LUCIA SANTIAGO"},
		Result:         &value.Value{Type: value.String, Value: "node4"},
		Comparer:       &compare.Equal{},
	}
	node1 := &ddt.Node{
		ID:             1,
		ParentID:       0,
		Children:       []*ddt.Node{node3, node4},
		ValueToCompare: &value.Value{Type: value.Bool, Value: true},
		PreProcessArgs: []*value.Value{{Type: value.String, Value: "FullName"}},
		PreProcessFn:   function.PreProcessFn{Function: function.CallStructMethod, Name: "CallStructMethod"},
		Comparer:       &compare.Equal{},
	}
	node2 := &ddt.Node{
		ID:             2,
		ParentID:       0,
		Children:       []*ddt.Node{node5, node6},
		ValueToCompare: &value.Value{Type: value.Bool, Value: false},
		Comparer:       &compare.Equal{},
		PreProcessArgs: []*value.Value{{Type: value.String, Value: "Age"}},
		PreProcessFn:   function.PreProcessFn{Function: function.GetStructAttribute, Name: "GetStructAttribute"},
	}
	root := &ddt.Node{
		Children:       []*ddt.Node{node1, node2},
		PreProcessArgs: []*value.Value{{Type: value.String, Value: "UnderAge"}},
		PreProcessFn:   function.PreProcessFn{Function: function.CallStructMethod, Name: "CallStructMethod"},
		ID:             0,
		ParentID:       -1,
	}
	userTree, err := ddt.NewTree("userTree", root)
	if err != nil {
		panic(err)
	}
	result, err := ddt.ResolveTree(userTree, &user{Age: 12, FirstName: "SANTIAGO", LastName: "LUCIA"})
	if err != nil {
		panic(err)
	}
	// 结果 node3
	fmt.Println(result.(string))
}

从JSON创建用户树

package main

import (
	"encoding/json"
	"fmt"
	"github.com/sgrodriguez/ddt"
)

type user struct {
	Age       int
	FirstName string
	LastName  string
}

func (u *user) UnderAge() bool {
	return u.Age < 18
}

func (u *user) FullName() string {
	return u.FirstName + " " + u.LastName
}

func main() {
	// 定义空树
	tree, err := ddt.NewTree("newTree", &ddt.Node{ID: 0, ParentID: -1})
	if err != nil {
		panic(err)
	}
	treeFromJson := []byte(`
{
   "nodes":[
      {
         "preProcessFnName":"CallStructMethod",
         "id":0,
         "parentId":-1,
         "preProcessFnArgs":[
            {
               "Value":"UnderAge",
               "Type":"string"
            }
         ]
      },
      {
         "preProcessFnName":"CallStructMethod",
         "id":1,
         "parentId":0,
         "preProcessFnArgs":[
            {
               "Value":"FullName",
               "Type":"string"
            }
         ],
         "comparer":{
            "type":"eq"
         },
         "valueToCompare":{
            "Value":true,
            "Type":"bool"
         }
      },
      {
         "preProcessFnName":"GetStructAttribute",
         "id":2,
         "parentId":0,
         "preProcessFnArgs":[
            {
               "Value":"Age",
               "Type":"string"
            }
         ],
         "comparer":{
            "type":"eq"
         },
         "valueToCompare":{
            "Value":false,
            "Type":"bool"
         }
      },
      {
         "preProcessFnName":"",
         "id":3,
         "parentId":1,
         "comparer":{
            "type":"eq"
         },
         "valueToCompare":{
            "Value":"SANTIAGO LUCIA",
            "Type":"string"
         },
         "result":{
            "Value":"node3",
            "Type":"string"
         }
      },
      {
         "preProcessFnName":"",
         "id":4,
         "parentId":1,
         "comparer":{
            "type":"eq"
         },
         "valueToCompare":{
            "Value":"LUCIA SANTIAGO",
            "Type":"string"
         },
         "result":{
            "Value":"node4",
            "Type":"string"
         }
      },
      {
         "preProcessFnName":"",
         "id":5,
         "parentId":2,
         "comparer":{
            "type":"lt",
            "equal":true
         },
         "valueToCompare":{
            "Value":30,
            "Type":"int"
         },
         "result":{
            "Value":"node5",
            "Type":"string"
         }
      },
      {
         "preProcessFnName":"",
         "id":6,
         "parentId":2,
         "comparer":{
            "type":"gt",
            "equal":false
         },
         "valueToCompare":{
            "Value":30,
            "Type":"int"
         },
         "result":{
            "Value":"node6",
            "Type":"string"
         }
      }
   ],
   "name":"userTree"
}`)
	err = json.Unmarshal(treeFromJson, tree)
	if err != nil {
		panic(err)
	}
	result, err := ddt.ResolveTree(tree, &user{Age: 12, FirstName: "SANTIAGO", LastName: "LUCIA"})
	if err != nil {
		panic(err)
	}
	// 结果 node3
	fmt.Println(result.(string))
	treeByte, err := json.Marshal(tree)
	fmt.Println(string(treeByte))
}

创建简单树

创建一个仅使用基本类型的简单树。

简单树示例

package main

import (
	"fmt"
	"github.com/sgrodriguez/ddt"
	"github.com/sgrodriguez/ddt/compare"
	"github.com/sgrodriguez/ddt/value"
)

func main() {
	leaf1 := ddt.Node{
		ID:             1,
		ParentID:       0,
		ValueToCompare: &value.Value{Value: int64(60), Type: value.Int64},
		Comparer:       &compare.Greater{},
		Result:         &value.Value{Value: "prize1", Type: value.String},
	}
	leaf11 := ddt.Node{
		ID:             3,
		ParentID:       2,
		ValueToCompare: &value.Value{Value: int64(30), Type: value.Int64},
		Comparer:       &compare.Equal{},
		Result:         &value.Value{Value: "prize2", Type: value.String},
	}
	leaf12 := ddt.Node{
		ID:             4,
		ParentID:       2,
		ValueToCompare: &value.Value{Value: int64(30), Type: value.Int64},
		Comparer:       &compare.Greater{},
		Result:         &value.Value{Value: "prize3", Type: value.String},
	}
	leaf13 := ddt.Node{
		ID:             5,
		ParentID:       2,
		ValueToCompare: &value.Value{Value: int64(30), Type: value.Int64},
		Comparer:       &compare.Lesser{},
		Result:         &value.Value{Value: "prize4", Type: value.String},
	}
	node1 := ddt.Node{
		Children:       []*ddt.Node{&leaf11, &leaf12, &leaf13},
		ID:             2,
		ParentID:       0,
		ValueToCompare: &value.Value{Value: int64(60), Type: value.Int64},
		Comparer:       &compare.Lesser{Equal: true},
	}
	root := ddt.Node{
		ID:       0,
		ParentID: -1,
		Children: []*ddt.Node{&node1, &leaf1},
	}
	simpleTree, err := ddt.NewTree("simpleTree", &root)
	if err != nil {
		panic(err)
	}
	result, err := ddt.ResolveTree(simpleTree,int64(15))
	if err != nil {
		panic(err)
	}
	fmt.Println(result.(string))
}

从JSON创建简单树

package main

import (
	"encoding/json"
	"fmt"
	"github.com/sgrodriguez/ddt"
)

func main() {
	// 定义空树
	tree, err := ddt.NewTree("newTree", &ddt.Node{ID: 0, ParentID: -1})
	if err != nil {
		panic(err)
	}
	treeFromJson := []byte(`
{
   "nodes":[
      {
         "preProcessFnName":"",
         "id":0,
         "parentId":-1
      },
      {
         "preProcessFnName":"",
         "id":2,
         "parentId":0,
         "comparer":{
            "type":"lt",
            "equal":true
         },
         "valueToCompare":{
            "Value":60,
            "Type":"int64"
         }
      },
      {
         "preProcessFnName":"",
         "id":1,
         "parentId":0,
         "comparer":{
            "type":"gt",
            "equal":false
         },
         "valueToCompare":{
            "Value":60,
            "Type":"int64"
         },
         "result":{
            "Value":"prize1",
            "Type":"string"
         }
      },
      {
         "preProcessFnName":"",
         "id":3,
         "parentId":2,
         "comparer":{
            "type":"eq"
         },
         "valueToCompare":{
            "Value":30,
            "Type":"int64"
         },
         "result":{
            "Value":"prize2",
            "Type":"string"
         }
      },
      {
         "preProcessFnName":"",
         "id":4,
         "parentId":2,
         "comparer":{
            "type":"gt",
            "equal":false
         },
         "valueToCompare":{
            "Value":30,
            "Type":"int64"
         },
         "result":{
            "Value":"prize3",
            "Type":"string"
         }
      },
      {
         "preProcessFnName":"",
         "id":5,
         "parentId":2,
         "comparer":{
            "type":"lt",
            "equal":false
         },
         "valueToCompare":{
            "Value":30,
            "Type":"int64"
         },
         "result":{
            "Value":"prize4",
            "Type":"string"
         }
      }
   ],
   "name":"simpleTree"
}`)
	err = json.Unmarshal(treeFromJson, tree)
	if err != nil {
		panic(err)
	}
	result, err := ddt.ResolveTree(tree, int64(15))
	if err != nil {
		panic(err)
	}
	// 结果 prize4
	fmt.Println(result.(string))
	// 修改树的某些属性,例如prize4的结果
	modifiedTree := []byte(`{"nodes":[{"preProcessFnName":"","id":0,"parentId":-1},{"preProcessFnName":"","id":2,"parentId":0,"comparer":{"type":"lt","equal":true},"valueToCompare":{"Value":60,"Type":"int64"}},{"preProcessFnName":"","id":1,"parentId":0,"comparer":{"type":"gt","equal":false},"valueToCompare":{"Value":60,"Type":"int64"},"result":{"Value":"prize1","Type":"string"}},{"preProcessFnName":"","id":3,"parentId":2,"comparer":{"type":"eq"},"valueToCompare":{"Value":30,"Type":"int64"},"result":{"Value":"prize2","Type":"string"}},{"preProcessFnName":"","id":4,"parentId":2,"comparer":{"type":"gt","equal":false},"valueToCompare":{"Value":30,"Type":"int64"},"result":{"Value":"prize3","Type":"string"}},{"preProcessFnName":"","id":5,"parentId":2,"comparer":{"type":"lt","equal":false},"valueToCompare":{"Value":30,"Type":"int64"},"result":{"Value": 420,"Type":"int64"}}],"name":"simpleTree"}`)
	err = json.Unmarshal(modifiedTree, tree)
	if err != nil {
		panic(err)
	}
	result, err = ddt.ResolveTree(tree, int64(15))
	if err != nil {
		panic(err)
	}
	// 结果 420
	fmt.Println(result.(int64))
}

概述

节点(Node)

  • ID: 节点的ID,根节点必须为0
  • ParentID: 父节点ID,根节点必须为-1
  • Result: 如果节点是叶子节点并且是树的下一个节点,这就是结果
  • Comparer: 比较器
  • ValueToCompare: 比较值
  • PreProcessFn: 在比较前预处理输入的函数
  • PreProcessArgs: 预处理参数

值(Value)

可用于比较、结果和作为PreProcessArgs的基本类型:

  • Int
  • Int64
  • Bool
  • String
  • Float64

比较器(Comparators)

  • Greater (或 Equal)
  • Lesser (或 Equal)
  • Equal

预处理函数(Pre-Process Functions)

在比较树的下一个级别之前预处理输入的函数:

  • CallStructMethod
  • GetStructAttribute

更多关于golang动态决策树规则引擎插件库ddt的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang动态决策树规则引擎插件库ddt的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang动态决策树规则引擎插件库DDT使用指南

DDT (Dynamic Decision Tree) 是一个轻量级的Go语言动态决策树规则引擎库,它允许开发者以灵活的方式定义和执行决策逻辑。

安装DDT

go get github.com/yourusername/ddt

基本概念

DDT核心概念包括:

  • 规则节点(RuleNode): 包含条件和动作的决策节点
  • 条件(Condition): 决定是否执行当前节点的判断逻辑
  • 动作(Action): 条件满足时执行的操作

基本使用示例

package main

import (
	"fmt"
	"github.com/yourusername/ddt"
)

func main() {
	// 创建决策树引擎
	engine := ddt.NewEngine()

	// 定义规则树
	tree := &ddt.TreeNode{
		Rule: &ddt.RuleNode{
			Name: "root",
			Condition: func(ctx ddt.Context) bool {
				// 检查年龄是否大于18
				age, ok := ctx.Get("age").(int)
				return ok && age > 18
			},
			Action: func(ctx ddt.Context) error {
				fmt.Println("成年人逻辑处理")
				return nil
			},
			Children: []*ddt.TreeNode{
				{
					Rule: &ddt.RuleNode{
						Name: "vip_check",
						Condition: func(ctx ddt.Context) bool {
							// 检查是否是VIP
							isVIP, ok := ctx.Get("is_vip").(bool)
							return ok && isVIP
						},
						Action: func(ctx ddt.Context) error {
							fmt.Println("VIP用户特殊处理")
							return nil
						},
					},
				},
			},
		},
	}

	// 注册决策树
	engine.Register("age_check", tree)

	// 执行决策树
	ctx := ddt.NewContext()
	ctx.Set("age", 25)
	ctx.Set("is_vip", true)

	err := engine.Execute("age_check", ctx)
	if err != nil {
		fmt.Printf("执行错误: %v\n", err)
	}
}

高级特性

1. 动态加载规则

// 从JSON加载规则
func LoadRulesFromJSON(jsonStr string) (*ddt.TreeNode, error) {
	var node ddt.TreeNode
	err := json.Unmarshal([]byte(jsonStr), &node)
	if err != nil {
		return nil, err
	}
	return &node, nil
}

// 使用示例
jsonRule := `{
	"rule": {
		"name": "dynamic_rule",
		"condition": "ctx.Get('score') > 60",
		"action": "ctx.Set('passed', true)"
	}
}`

tree, err := LoadRulesFromJSON(jsonRule)
if err != nil {
	// 处理错误
}
engine.Register("dynamic_check", tree)

2. 插件系统

// 自定义条件插件
type ScoreCondition struct{}

func (c *ScoreCondition) Eval(ctx ddt.Context) bool {
	score, ok := ctx.Get("score").(int)
	return ok && score > 80
}

// 自定义动作插件
type NotifyAction struct{}

func (a *NotifyAction) Execute(ctx ddt.Context) error {
	email, ok := ctx.Get("email").(string)
	if ok {
		fmt.Printf("发送通知到: %s\n", email)
	}
	return nil
}

// 注册插件
engine.RegisterCondition("high_score", &ScoreCondition{})
engine.RegisterAction("send_notify", &NotifyAction{})

// 使用插件
tree := &ddt.TreeNode{
	Rule: &ddt.RuleNode{
		Name:       "plugin_demo",
		Condition:  "high_score",
		Action:     "send_notify",
	},
}

3. 规则组合

// 组合多个规则
compositeRule := &ddt.TreeNode{
	Rule: &ddt.RuleNode{
		Name: "composite",
		Condition: func(ctx ddt.Context) bool {
			return engine.Evaluate("rule1", ctx) && 
			       engine.Evaluate("rule2", ctx)
		},
		Action: func(ctx ddt.Context) error {
			_ = engine.Execute("action1", ctx)
			return engine.Execute("action2", ctx)
		},
	},
}

最佳实践

  1. 规则分离: 将业务规则与核心逻辑分离,便于维护
  2. 性能考虑: 对于高频执行的规则,考虑预编译条件
  3. 错误处理: 为所有动作添加适当的错误处理
  4. 日志记录: 记录决策过程和结果,便于调试

性能优化

// 预编译条件表达式
type CompiledCondition struct {
	expr *govaluate.EvaluableExpression
}

func (c *CompiledCondition) Eval(ctx ddt.Context) bool {
	result, err := c.expr.Evaluate(ctx.ToMap())
	if err != nil {
		return false
	}
	return result.(bool)
}

// 使用预编译条件
expr, _ := govaluate.NewEvaluableExpression("age > 18 && score > 60")
engine.RegisterCondition("compiled_cond", &CompiledCondition{expr: expr})

DDT提供了灵活的方式来构建和管理复杂的业务规则,特别适合需要频繁变更决策逻辑的场景。通过合理设计规则树结构,可以大大提高系统的可维护性和扩展性。

回到顶部