Excel VBA 中的基本规则引擎
VBA/Excel 2013 中的一个粗略专家系统
引言
开源专家系统中,大部分是用 Java、Prolog 等编写的——我没有找到任何用 VBA 编写的。我想要一个粗略的规则引擎,主要用于在实现一个健壮的引擎之前进行原型开发。语法非常简单,几乎任何人都可以编写规则——这就是我想要的。
这个例子展示了在开始一个项目时定义使用何种方法的规则。
Excel 工作簿包含一个自我解释的帮助文档。
背景
正向链式工作规则引擎的原理很简单
我们考虑一组规则,例如:
IF temperature < 32 THEN water_state = "ice"
IF water_state = "ice" THEN skating_possible = "TRUE"
每条规则都由条件和结论组成。
每条规则的组成是变量运算符变量或常量。
我们考虑一组事实,例如:
temperature = 23
weather = "SUNNY
每条事实的组成是变量=变量或常量。
- 是否可以滑冰?
我们想确定变量 skating_possible 是 TRUE 还是 FALSE。
规则引擎将尝试根据已知的事实应用规则。当一条规则被触发时,规则的所有结论都会变成新的事实,事实表会不断增长,直到找到目标或没有规则可以触发为止。
有关专家系统的更多详细信息,请参阅相关文献。
Using the Code
结构
该程序处理以下结构:变量、表达式和规则。变量是操作的基本对象(例如 X
、Temperature
、Age
),表达式是使用变量的逻辑表达式(例如 X = 3
或 Temperature > 32
)。规则将表达式组合在一起:构成规则条件部分的表达式和构成规则结论部分的表达式。
为了方便操作这些对象,我创建了类。
cVariable 类
Private sName As String 'Name of the variable.
Private sType As String 'String, Integer, Boolean, Date
Private sQuestion As String 'Question being asked to get the value of the variable.
Private bQuestionAsked As Boolean 'To prevent asking several times the same question.
Private collPossibleValues As Collection
Private collRulesIF As Collection 'Rules names where this variable appears in the condition.
注释
- 有些变量仅限于某些值(例如,布尔变量实际上是一个
string
变量,它只能包含“TRUE
”或“FALSE
”)。collPossibleValues
集合将有助于实现这些限制。 collRulesIF
集合包含所有将该变量放在条件部分中的规则的名称。因此,当给变量赋值时,可以轻松获取所有需要重新评估的规则。- 变量的值(如果存在)保存在事实表中。
- 有时,引擎无法触发任何规则,需要通过询问用户关于某些变量的值来获取额外信息:这就是
sQuestion
的原因。 - 如果用户不知道问题的答案,第二次询问就没有意义了:这就是
bQuestionAsked
的原因。
cVariable
的实例保存在 cVariables
类(集合)中。
cExpression 类
逻辑表达式由以下部分组成:
- 一个变量
- 后面跟着 0 个或 1 个运算符
- 如果有一个运算符,后面跟着一个变量或一个常量。
示例
WEATHER = "NICE"
(变量WEATHER
和常量NICE
)TEMPERATURE = 32
(变量TEMPERATURE
和常量32
)TEMP1 <> TEMP2
(变量TEMP1
和变量TEMP2
)NOT (NEW STUFF)
(变量NEW STUFF
的否定)
Private sWord1 As String 'Always a declared variable.
Private sOperator As String 'Nothing or a valid operator.
Private sWord2 As String 'Nothing or a declared variable or a constant
Private bWord2IsVariable As Boolean
Private sKey As String 'Concatenation sWord1&sOperator&sWord2
Private iValue As Integer '0 = Unknown / 1 <=> True / -1 <=> False
注释
- 表达式总是以变量作为第一个元素。
- 实际上,我们可以每次通过查找
cVariables
来检索第二个单词是否是变量,但将其作为表达式的一部分可以节省时间。 - 键用于确保表达式在条件或结论中的唯一性。
- 如上所述,值记录表达式是
true
、false
还是尚未评估。
cExpression
的实例保存在 cExpressions
类(集合)中。
cRule 类
规则由以下部分组成:
- 一个用于标识规则的名称(唯一)
- 条件(例如,表达式,例如规则的
IF
部分) - 结论(例如,表达式,例如规则的
THEN
部分)
Private sRuleName As String 'Rule name.
Private collIF As cExpressions 'Conditions within the rule.
Private collTHEN As cExpressions 'Conclusions within the rule.
Private bCanBeFired As Boolean 'True <=> all conditions are true.
Private bActivated As Boolean 'Rule has been fired already.
注释
- 规则的结论是为变量赋值的表达式(例如
X = 3
)。结论不能是X <> 0
这样的表达式。 - 布尔值
bCanBeFired
用于轻松确定规则是否可以执行(例如,所有条件都为TRUE
)。 - 布尔值
bActivated
可防止规则被触发多次。
cExpression
的实例保存在 cExpressions
类(集合)中。
算法
非常简单
- 加载变量
- 加载规则
- 获取目标(例如,引擎必须找到值的变量)
- 获取初始事实
- 当(可以触发一些规则)和(目标未找到)时
- 触发“最佳”规则。
- 将所有结论添加到事实表
- 如果达到目标,则停止
- Wend
- 如果找到目标,则显示其值,否则显示“无解决方案”。
选择要触发的“最佳”规则。
这是一个棘手的问题。这里使用的是选择方法:
- 识别所有尚未触发、所有条件都满足且目标包含在其结论中、并且随机选择一条。如果没有候选规则
- 识别所有尚未触发、所有条件都满足的规则——即使目标不包含在其结论中——并且随机选择一条。如果没有候选规则
- 识别所有尚未触发、目标包含在其结论中但某些条件未知的所有规则。然后询问用户条件中变量的值。如果可以触发一条规则,则选择它。如果没有候选规则
- 识别所有尚未触发、但某些条件未知的所有规则。然后询问用户条件中变量的值。如果可以触发一条规则,则选择它。如果没有候选规则……则无法触发任何规则。
技巧
一旦一个事实变为 true
(例如,AGE = 33
),并且它被存储在事实表中,立即重新评估所有在其条件中包含相应变量(例如 AGE
)的规则。
兴趣点
这里可能存在一些错误,引擎还可以进一步优化。可以添加一些有用的功能,例如:
- 添加反向链式
- 添加结论的概率(类似于 MYCIN)
- 能够处理结论中的函数,例如
X = X + Y
...
历史
- 版本 2.0 - 文章在被拒绝后重写