用规则匹配对象属性的算法
本文关键字:属性 算法 对象 规则 | 更新日期: 2023-09-27 18:14:23
对标题不清楚表示歉意。下面是解释:
我有一个对象类型Foo,它有属性a b c d。假设这些属性(string/bool类型)中的每一个都可以有3个唯一的值(a有1,2,3;B有11、12、13等等)。
我有一组规则来匹配Foo对象列表。规则可以有一个或多个选择值的属性。例子:规则1:a=1规则2:b=12 and a=2
我想知道什么是获得匹配规则的最佳方式(基于c#/Haskell的解决方案更可取,尽管只是算法的解释也很好)。
我提到c#是因为我很感兴趣,如果有任何可能的方法,我们可以使用LINQ来进行这种匹配。Haskell被认为是函数式语言的代理,因此是一种递归的、无分支的方法。我目前正在使用字典来构建规则,然后使用反射来完成匹配。我最喜欢当前解决方案的一点是,如果我们需要添加一个新属性,那么它很容易,而且由于分支较少,代码非常容易理解。
添加一个示例以更清晰
我们有一个具有以下属性的动物对象列表
Object:Animal
Properties: Color, LivingEnvironment, Place, Mammal (all properties are of type string)
数据:
Animal1 : Red, Water, Arctic, No
Animal2 : Black, Land, Asia, No
Animal3 : Blue, Land, UK, Yes
Rule1 : Color=Red And LivingEnvironment=Land
Rule2 : Color=Red And LivingEnvironment=Water
Rule3 : COlor=Blue And Place=UK And Mammal=Yes
规则可以从用户界面配置,因此在编译时不知道它们。潜在的用户可以将规则3更改为新的定义
Rule3 : Color=Blue And PLace=UK
我希望这能澄清之前引起的一些混淆。
规则只是一个函数:
type Rule = Foo -> Bool
下面是一个创建规则的函数:
(=:=) :: Eq a => (Foo -> a) -> a -> Rule
f =:= x = 'foo -> f foo == x
(如。a =:= 1
)
下面是几个组合规则的函数:
allRules, anyRules :: [Rule] -> Rule
allRules rules foo = all ($ foo) rules
anyRule rules foo = any ($ foo) rules
(如。allRules [b =:= 12, a =:= 2]
)
使用标准的filter
函数来过滤您的[Foo]
。
你想从配置文件中读取你的规则。我假设你从读取/解析你的配置中得到一个字符串对列表。
让我们从一个将一对字符串转换为规则的函数开始:
readRule :: String -> String -> Maybe Rule
readRule = fieldName requiredValue = do
constructRule <- lookup fieldName ruleDefs
constructRule requiredValue
ruleDefs :: [(String, String -> Maybe Rule)] -- should be a Map irl
现在让我们编写一个辅助函数来生成ruleDefs
中的条目:
ruleEntry :: (Read a, Eq a) => String -> (Foo -> a) -> String -> Maybe Rule
ruleEntry name project = (name, constructRule) where
constructRule requiredValue
= case filter (null . snd) (reads requiredValue) of
[(value, _)] -> Just (value ==)
_ -> Nothing
除了这个辅助函数,您还可以手工编写ruleDefs
:
ruleDefs = [
ruleEntry "alpha" alpha,
ruleEntry "beta" beta,
ruleEntry "gamma" gamma,
ruleEntry "delta" delta]
这种构造既适用于字段(例如data Foo = Foo { alpha :: Int, beta :: Int }
中的alpha
和beta
),也适用于计算字段(例如delta foo = alpha foo - beta foo
)。我将展示两种构建ruleDefs
的技术,它们都将使用Template Haskell。
如果你只有一条规则:
你所说的规则是非常简单的谓词,或规范。我将向您展示在c#中根据某些规则过滤Foo
对象集合的两种方法。对于这两个例子,我们假设我们有一个Foo[] foos
:
Func<Foo, bool>
委托类型适用于Foo
对象上的谓词函数:
Func<Foo, bool> someRule = foo => foo.a == 2 && foo.b == 12;
IEnumerable<Foo> matchingFoos = foos.Where(someRule);
没有LINQ的c#示例:
在各种Func<>
委托类型出现之前,.NET类库已经有了适合这里的Predicate<T>
:
Predicate<Foo> someRule = delegate(Foo foo) { return foo.a == 2 && foo.b == 12; };
Foo[] matchingFoos = Array.FindAll(foos, someRule);
(注意,与基于linq的解决方案不同,此解决方案返回一个集合,而不是惰性求值序列。此外,匿名委托和lambda语法的选择与LINQ无关,但我在非LINQ示例中选择了较旧的语法,因为这是当时c#语言的样子,在版本3和引入LINQ之前。
当你有几个规则时:
然后在匹配Foo
对象之前,必须以某种方式将它们组合起来。也就是说,您需要决定Foo
是否必须匹配所有规则(逻辑与),或者至少匹配一个规则(逻辑或),等等。您可以从两个给定的规则中派生组合规则,如下所示:
static Func<Foo, bool> And(this Func<Foo, bool> ruleA, Func<Foo, bool> ruleB)
{
return x => ruleA(x) && ruleB(x);
}
static Func<Foo, bool> Or(this Func<Foo, bool> ruleA, Func<Foo, bool> ruleB)
{
return x => ruleA(x) || ruleB(x);
}
Func<Foo, bool> ruleA = foo => foo.a == 2;
Func<Foo, bool> ruleB = foo => foo.b == 12;
Func<Foo, bool> combinedRule = ruleA.And(ruleB);
既然你允许你的用户定义规则,你可能不想在这样的规则中硬连接常量;所以你可以创建工厂方法(或类),例如:
Func<Foo, bool> PropertyAEquals(int value)
{
return foo => foo.a == value;
}
Func<Foo, bool> PropertyBEquals(int value)
{
return foo => foo.b == value;
}
你可以让它像你想的那样灵活。您所需要的只是返回Func<Foo, bool>
的工厂方法或类,以及将用户输入从UI转换为对正确工厂方法的调用所需的逻辑。
public bool AbidingByRule(Dictionary<string,object> rule)
{
var type=this.GetType();
int unmatchedCount=rule.Count(r => !r.Value.Equals(type.GetProperty(r.Key).GetValue(this, null)));
return unmatchedCount == 0;
}
您可以使用NCalc
(link)来解析简单的表达式作为规则,例如:
class Foo
{
public int a { get; set; }
public int b { get; set; }
public int c { get; set; }
}
static bool VerifyRule(Foo obj, string rule)
{
NCalc.Expression expr = new NCalc.Expression(rule);
expr.EvaluateParameter += (name, args) =>
{
args.Result = typeof(Foo).GetProperty(name).GetValue(obj, null);
};
return (bool)expr.Evaluate();
}
// USAGE EXAMPLE
static void Main(string[] args)
{
var foo1 = new Foo() { a = 3, b = 4, c = 12 };
var foo2 = new Foo() { a = 1, b = 4, c = 12 };
// verify rules
var res1 = VerifyRule(foo1, "a == 3 && b == 4"); // returns true
var res2 = VerifyRule(foo2, "a == 3 && b == 4"); // returns false
// more complex rules:
var res3 = VerifyRule(foo1, "(a < 4 && b > 5) || c == 12"); // returns true
var res4 = VerifyRule(foo1, "a + b == 7"); // returns true
}
注意:
这里我还是用反射。在我看来,你无法避免,因为你的规则是通过UI动态定义的…