2

为不清楚的标题道歉。这是解释:

我有一种具有属性 a、b、c、d 的对象 Foo。假设这些属性中的每一个(字符串/布尔类型)都可以单独具有 3 个唯一值(a 具有 1、2、3;b 具有 11、12、13 等等)。

我有一组规则,我想根据这些规则匹配 Foo 对象列表。规则可以具有一个或多个具有选定值的属性。示例:规则 1:a=1 规则 2:b=12 和 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

我希望这可以澄清之前引起的一些混乱。

4

4 回答 4

2

规则只是一个函数:

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]

此构造适用于字段(例如alphabetain data Foo = Foo { alpha :: Int, beta :: Int })和计算字段(例如delta foo = alpha foo - beta foo)。我将展示一些无需重复输入的构建技术ruleDefs,它们都将使用 Template Haskell。

(还有更多。)

于 2013-05-18T08:39:24.180 回答
1

如果您只有一个规则:

您所说的规则是非常简单的谓词规范。我将向您展示Foo根据某些规则在 C# 中过滤对象集合的两种方法。对于这两个示例,假设我们有一个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对象之前以某种方式组合它们。也就是说,您需要决定 a 是否Foo必须匹配所有规则(逻辑 AND),或者至少匹配一个(逻辑 OR)等。您可以从两个给定的规则中导出组合规则,如下所示:

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 转换为对正确工厂方法的调用所需的逻辑。

于 2013-05-18T09:45:24.107 回答
0
 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;
         }
于 2013-05-18T07:54:59.897 回答
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 动态定义的......

于 2013-05-18T08:21:51.037 回答