views:

701

answers:

3

I have essentially a survey that is shown, and people answer questions a lot like a test, and there are different paths, it is pretty easy so far, but i wanted to make it more dynamic, so that i can have a generic rule that is for the test with all the paths, to make the evaluator easier to work with currently i just allow AND's, and each OR essentially becomes another Rule in the set,

QuestionID, then i form a bunch of AND rules like so

<rule id="1">
<true>
 <question ID=123>
 <question ID=124>
</true>
<false>
 <question ID=127>
 <question ID=128>
</false>
</rule>
<rule id="2"><true>
 <question ID=123>
 <question ID=125>
</true>
<false>
 <question ID=127>
</false>
</rule>

this rule 1 says if question 123, and 124 are answered true, and 127, 128 are false, they pass. OR (rule 2) is if 123 and 125 are true and 127 is false, they pass as well. This gets tedious if there are many combinations, so i want to implement OR in the logic, I am just not sure what best approach is for this problem.

I think rules engine is too complicated, there must be an easier way, perhaps constructing a graph like in LINQ and then evaluating to see if they pass,

thanks!

--not an compsci major.

+1  A: 

This doesn't have to be complicated: you're most of the way already, since your and elements effectively implement an AND-type rule. I would introduce an element that can hold and elements.

In your could, you could have:

  • A RuleBase class, with a "public abstract bool Evaluate()" method
  • TrueRule, FalseRule and OrRule classes, which contain lists of RuleBase objects
  • A QuestionRule class, which refers to a specific question

You would implement the Evaluate method on each of these as follows:

  • TrueRule: returns true only if all the contained rules return true from Evaluate
  • FalseRule: returns true only if all the contained rules return false from Evaluate
  • OrRule: returns true if at least one of the contained rules returns true from Evaluate
  • QuestionRule: returns the answer to the original question

This class hierarchy implements a simple abstract syntax tree (AST). LINQ, in the form of the System.Expressions.Expression class, does pretty much the same thing, but it's helpful to write your own if it's not obvious how everything fits together.

Tim Robinson
A: 

I'm not sure I totally understand the problem you are trying to solve but you could use a simple XPath to get at the ID's:

This would give you all of the "true" ID's where the rule ID = 1: /rule[@id="1"]/true//@ID

Same as above only it gives you the false ID's: /rule[@id="1"]/false//@ID

Lastly a link to an introduction to XPath in .NET http://www.developer.com/xml/article.php/3383961

Good Luck

toddk
A: 

I'd suggest putting the answers on the questions, rather than using true and false to group the questions. I think that it makes for XML that's easier to read, which is debatable. What's not debatable is that it makes it possible to evaluate a question element independently, i.e. without any knowledge of the context in which you're trying to evaluate it. That makes for simpler code.

I'd also take a page from XML Schema and implement your OR logic as a choice element. A choice element is true if any of its children are true. You can, of course, nest them:

<rule id="1">
   <question id="123" answer="true" />
   <question id="124" answer="false" />
   <choice id="1">
      <question id="125" answer='true' />
      <choice id="2">
         <question id="126" answer='false' />
         <question id="127" answer='false' />
      </choice>
   </choice>
</rule>

This leaves you with four pretty simple methods to implement, each of which is used by the one preceding it:

  • bool GetProvidedAnswer(int questionID)
  • bool IsQuestionCorrect(XmlElement question)
  • bool IsChoiceCorrect(XmlElement choice)
  • bool IsRuleSatisfied(XmlElement rule)

The structure of the XML makes these methods pretty simple to implement:

 bool IsRuleSatisfied(XmlElement rule)
 {
    bool satisfied = true;
    foreach (XmlElement child in rule.SelectNodes("*"))
    {
       if (child.Name == "question")
       {
          satisfied = satisfied && IsQuestionCorrect(child);
       }
       if (child.Name == "choice")
       {
          satisfed = satisfied && IsChoiceCorrect(child);
       }
       if (!satisfied)
       {
          return false;
       }
   }
   return true;
}

It might be worth adding a List<XmlElement> to the parameters of the IsFooCorrect methods. (If the rule engine is in a class, you could make it a class field.) Make `all of the methods add the current element to the list when an answer's wrong. You can then examine the contents of that list to know exactly why a rule failed.

Robert Rossney