用规则匹配对象属性的算法

本文关键字:属性 算法 对象 规则 | 更新日期: 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 }中的alphabeta),也适用于计算字段(例如delta foo = alpha foo - beta foo)。我将展示两种构建ruleDefs的技术,它们都将使用Template Haskell。

如果你只有一条规则:

你所说的规则是非常简单的谓词,或规范。我将向您展示在c#中根据某些规则过滤Foo对象集合的两种方法。对于这两个例子,我们假设我们有一个Foo[] foos:

使用LINQ的c#示例:

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动态定义的…