提问人:jon hanson 提问时间:9/16/2023 最后编辑:jon hanson 更新时间:9/16/2023 访问量:66
优化简单的规则匹配逻辑
Optimise simple rule-matching logic
问:
我有一个简单的算法用于一些规则匹配逻辑,想看看是否有办法优化它。
问题如下 - 我有一组规则,我需要确定每个传入记录的规则(如果有)与记录匹配。该记录实质上是字符串属性到字符串值的映射。该规则由整数 ID 和条件列表组成。每个条件都有一个属性、一个运算符(IN 或 NOT_IN)和一组字符串值。
因此,示例规则可能如下所示:
{
id: "1",
conditions: [
{
attribute: "firstname",
operator: "IN",
values: ["Jon", "Fred"]
},{
attribute: "surname",
operator: "NOT_IN",
values: ["Smith"]
}
]
}
上述规则将匹配 record.get(“firstname”) 为 “Jon” 或 “Fred”,而 record.get(“surname”) 为 null 或不是 “Smith” 的任何记录。
应用于单条记录时的规则匹配逻辑如下:
- 遍历每个规则:
- 循环访问规则的每个条件:
- 从记录属性映射中查找条件属性。
- 如果条件运算符为 IN,则应用以下检查:
- 如果映射包含该属性,并且查找值在条件值列表中,则条件将通过,否则如果失败。
- 否则,如果条件运算符NOT_IN则应用以下检查:
- 如果映射不包含该属性,或者查找值不在条件值列表中,则条件将通过,否则将失败。
- 如果所有条件都通过,则该规则是匹配项,并且不考虑其他规则。
- 循环访问规则的每个条件:
如果不清楚,则列表中规则的顺序很重要,因为将选择匹配项的第一条规则。
我在下面包含了一些实现 Rules 数据模型和上述算法的 Java 代码。
上述逻辑实现起来相对简单,但它涉及遍历每条记录的规则。我的问题是是否有更有效的算法。
更多数据点:
- 这些规则基本上是静态的。
- 大约有 20 亿条记录需要处理。
- 这些记录将分批到达,大小不一,但通常为 100,000 - 1,000,000。
- 这些属性是预先知道的 - 总共大约有 500 个,但规则只使用相对较小的子集 - 可能不超过 20 个。
- 属性值通常不是事先知道的。
- 每个条件通常包含少量值 - 通常不超过 6 个。
- 组成规则的条件将分别使用不同的属性 - 即,您永远不会获得具有两个或多个条件使用相同属性的规则。
- 构成规则的条件数量通常很少,通常不超过 4 个。
为了优化算法,我研究了将规则预编译为数据结构的方法,这可能会加快规则处理速度。起初,我认为决策树结构可能更理想,但目前尚不清楚如何构建决策树结构,因为每个规则可能都在测试不同的属性集。我想到的另一种方法是将每个规则编码为数字,可能是位图或哈希码。
import java.util.*;
public class RulesTest {
// Data model.
enum Operator {IN, NOT_IN}
record Condition(String attribute, Operator operator, Set<String> values) {}
record Rule(int id, List<Condition> conditions) {}
// Test data.
private static final List<Rule> TEST_RULES = List.of(
new Rule(
1,
List.of(
new Condition("B", Operator.IN, Set.of("b1", "b2")),
new Condition("C", Operator.IN, Set.of("c2", "c4"))
)
), new Rule(
2,
List.of(
new Condition("A", Operator.IN, Set.of("a1", "a3")),
new Condition("C", Operator.NOT_IN, Set.of("c1", "c4")),
new Condition("D", Operator.IN, Set.of("d1", "d2", "d3"))
)
), new Rule(
3,
List.of(
new Condition("B", Operator.NOT_IN, Set.of("b1", "b4")),
new Condition("D", Operator.NOT_IN, Set.of("d1", "d2")),
new Condition("E", Operator.IN, Set.of("e1"))
)
)
);
// Matching logic.
private static OptionalInt match(List<Rule> rules, Map<String, String> attrValues) {
for (Rule rule : rules) {
final boolean pass = rule.conditions
.stream()
.allMatch(condition -> {
final String attrValue = attrValues.get(condition.attribute);
return (attrValue != null && condition.values().contains(attrValue))
== (condition.operator == Operator.IN);
});
if (pass) {
return OptionalInt.of(rule.id);
}
}
return OptionalInt.empty();
}
private static void test(List<Rule> rules, Map<String, String> attrValues) {
final OptionalInt optRuleId = match(rules, attrValues);
System.out.println(attrValues + " -> " + optRuleId);
}
public static void main(String[] args) {
{
final Map<String, String> attrValues = Map.of(
"A", "a1",
"B", "b1",
"C", "c1"
);
test(TEST_RULES, attrValues);
}
{
final Map<String, String> attrValues = Map.of(
"B", "b2",
"C", "c2"
);
test(TEST_RULES, attrValues);
}
{
final Map<String, String> attrValues = Map.of(
"A", "a3",
"C", "c3",
"D", "d3"
);
test(TEST_RULES, attrValues);
}
}
}
答:
0赞
Amit Jain
9/16/2023
#1
我们可以遍历属性,而不是遍历规则,并只运行那些检查特定属性的规则。
如果为可应用于 attribute() 的规则构建映射,则可以稍微提高性能。attributeRuleMap
如下所示
class Rule {
int id;
Map<String, Condition> conditionsMap = new HashMap<>();
public Rule(int id, List<Condition> conditions) {
this.id = id;
conditions.forEach(condition -> {
attributeRuleMap.computeIfAbsent(condition.attribute, x -> new ArrayList<>()).add(id);
conditionsMap.put(condition.attribute, condition);
});
ruleMap.put(id, this);
}
}
conditionsMap
仅用于访问我们正在检查规则的特定属性的条件。
那么你的匹配函数可以如下所示
private static List<Integer> match(Map<String, String> attrValues) {
List<Integer> rules = new ArrayList<>();
attrValues.forEach((key, value) ->
attributeRuleMap.get(key)
.forEach(rule -> {
Condition condition = ruleMap.get(rule).conditionsMap.get(key);
if (condition.values.contains(value) && condition.operator == Operator.IN) {
rules.add(rule);
}
}
));
return rules;
}
评论
0赞
jon hanson
9/17/2023
Not sure what your code is doing. I don't think it would work, and even if it did I don't think it would be faster than the simple algorithm.
评论