道歉标题不清楚。这是解释:
我有一种具有属性a,b,c,d的对象Foo。让我们说这些属性(string / bool的类型)可以分别具有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
我希望这可以澄清之前造成的一些混乱。
答案 0 :(得分: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]
此构造适用于字段(例如alpha
中的beta
和data Foo = Foo { alpha :: Int, beta :: Int }
)和计算字段(例如delta foo = alpha foo - beta foo
)。我将展示一些用于构建ruleDefs
的技术,而不需要重复键入,并且它们都将使用模板Haskell。
(更多信息。)
答案 1 :(得分:1)
您所谓的规则很简单predicates或specifications。我将根据一些规则向您展示两种在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
是否必须匹配所有规则(逻辑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转换为对正确工厂方法的调用所需的逻辑。
答案 2 :(得分: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;
}
答案 3 :(得分: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动态定义的......