import java.util.*; import java.awt.FlowLayout; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.*; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; /** * RuleBaseSystem * */
public class RuleBaseSystem extends JApplet implements ActionListener { static RuleBase rb;
JPanel p1 = new JPanel(); JPanel p2 = new JPanel();
String[] s = { "my-car is inexpensive", "my-car is expensive",
"my-car is small", "my-car is big", "my-car needs a lot of gas",
"my-car is a hybrid car", "my-car has Toyota's logo", "my-car has Honda's logo", "my-car is a popular car", "my-car has a VTEC engine", "my-car has a big engine", "my-car has several seats", "my-car has only 2 seats", "my-car is a wagon", "my-car is a sports car",
"my-car has several color models", "my-car is red", "my-car is stylish", "my-car is a good face", "my-car has an aluminium body" };
JLabel la = new JLabel("ルールベースシステム"); JList li = new JList(s);
JButton b = new JButton("→"); JTextArea ta = new JTextArea();
JScrollPane sp1 = new JScrollPane(li); JScrollPane sp2 = new JScrollPane(ta); int width = 600;
int top = 30; int bottom = 250;
public void init() { setLayout(null);
setSize(width, top + bottom); p1.setLayout(new FlowLayout()); p1.add(la);
p1.setBounds(0, 0, width, top); p2.setLayout(new GridLayout(1, 3)); p2.add(sp1);
b.addActionListener(this); p2.add(b);
p2.add(sp2);
p2.setBounds(0, top, width, bottom); add(p1);
add(p2); }
@Override
public void actionPerformed(ActionEvent arg0) { if (li.getSelectedIndex() == -1) JOptionPane.showMessageDialog(null, "何も選択されていま せん"); else { rb = new RuleBase(li.getSelectedValues()); printResult((rb.forwardChain())); } }
public void printResult(String result) {
StringTokenizer st = new StringTokenizer(result.trim(), ",[]"); String s = ""; String t; while (st.hasMoreTokens()) { t = st.nextToken(); s += t + "\n"; } ta.setText(s); } } /** * ワーキングメモリを表すクラス. * * */ class WorkingMemory { // アサーションは現在の状態の集合 ArrayList<String> assertions; WorkingMemory() {
assertions = new ArrayList<String>(); } /** * 前件にマッチするアサーションに対するバインディング情報を返す<br/> * 再帰的に matchable メソッドを呼び出して変数の束縛情報を得る<br/> * (バインディング≡変数束縛情報) * * @param 前件を示す * ArrayList * @return バインディング情報が入っている ArrayList */
ArrayList<String> theAntecedents) {
// 複数の変数の束縛を保存するためのハッシュマップのリスト
ArrayList<HashMap<String, String>> bindings = new ArrayList<HashMap<String, String>>();
// 照合開始
return matchable(theAntecedents, 0, bindings); } /** * 実際に前件にマッチするアサーションに対するバインディング情報を返すメ ソッド * * @param theAntecedents * 前件を表す ArrayList * @param n * マッチする回数(前件の数だけ) * @param bindings * これまでの変数の束縛情報のリスト * @return 新たに分かった変数の束縛情報リスト */
private ArrayList<HashMap<String, String>> matchable( ArrayList<String> theAntecedents, int n, ArrayList<HashMap<String, String>> bindings) { // 全ての前件に対してチェックが終わったら if (n == theAntecedents.size()) { // これまでの束縛情報を返す return bindings; } // 1回目 else if (n == 0) { // 成功フラグ
boolean success = false; // アサーションの数だけループ
for (int i = 0; i < assertions.size(); i++) { // 変数の束縛を保存するハッシュマップの作成
HashMap<String, String>(); // 前件とアサーションを照合 if ((new Matcher()).matching(theAntecedents.get(n), assertions.get(i), binding)) { // 解った束縛情報の更新 bindings.add(binding); // 成功のフラグを立てる success = true; } } // 1回でも成功した場合は次の前件について照合 if (success) {
return matchable(theAntecedents, n + 1, bindings); } // 1回も成功しなかったら失敗 else { return null; } } // 2回目以降 else { // 成功フラグ
boolean success = false;
// 新たな束縛情報のリストの作成
ArrayList<HashMap<String, String>> newBindings = new ArrayList<HashMap<String, String>>();
// これまでの束縛情報の候補だけループ for (int i = 0; i < bindings.size(); i++) {
for (int j = 0; j < assertions.size(); j++) {
if ((new Matcher()).matching(theAntecedents.get(n),
assertions.get(j), bindings.get(i))) {
newBindings.add(bindings.get(i)); success = true; } } } // 1回でも成功したら if (success) { return matchable(theAntecedents, n + 1, newBindings); } // 1回も成功しなかった時 else { return null; } } } /** * アサーションをワーキングメモリに加える. * * @param アサーションを表す文字列 */
public void addAssertion(String theAssertion) { System.out.println("ADD:" + theAssertion); assertions.add(theAssertion); } /** * 指定されたアサーションがすでに含まれているかどうかを調べる. * * @param アサーションを表す * String
*/
public boolean contains(String theAssertion) { return assertions.contains(theAssertion); } /** * ワーキングメモリの情報をストリングとして返す. * * @return ワーキングメモリの情報を表す String */
public String toString() {
return assertions.toString(); } } /** * ルールベースを表すクラス. * * */ class RuleBase { String fileName; FileReader f; StreamTokenizer st; WorkingMemory wm; ArrayList<Rule> rules; /** * コンストラクタ * * @param assertion * リストで選ばれた状態の一覧 */
public RuleBase(Object[] assertion) { // ファイルのパス
fileName = "???/CarShop.data"; // ワーキングメモリーの生成 wm = new WorkingMemory();
for (int i = 0; i < assertion.length; i++) // String 型に直して追加
wm.addAssertion(String.valueOf(assertion[i])); rules = new ArrayList<Rule>();
// ルールの読み込み loadRules(fileName); } /** * 前向き推論を行うためのメソッド<br/> * 新しいアサーションが生成されなくなるまで以下を続ける<br/> * <ol> * <li>前件とアサーションのマッチングを行う</li> * <li>マッチングに成功したら後件を新たなアサーションとしてワーキングメ モリに追加</li> * </ol> */
public String forwardChain() { boolean newAssertionCreated; // 新しいアサーションが生成されなくなるまで続ける. do { // 新しくアサーションが生成されたかどうかのフラグ newAssertionCreated = false; // ルールの数だけループ
for (int i = 0; i < rules.size(); i++) { // ルールを先頭から取得
Rule aRule = rules.get(i); // 一応表示 System.out.println("apply rule:" + aRule.getName()); // 取得したルールの前件を取得 ArrayList<String> antecedents = aRule.getAntecedents();
// 取得したルールの後件を取得
String consequent = aRule.getConsequent(); // マッチング開始
ArrayList<HashMap<String, String>> bindings = wm
.matchingAssertions(antecedents);
// 変数の束縛情報がない→(マッチング失敗) if (bindings != null) {
// 束縛情報のリスト分ループ
for (int j = 0; j < bindings.size(); j++) { // 後件をインスタンシエーション String newAssertion = instantiate((String) consequent, bindings.get(j)); // ワーキングメモリーになければ 成功 if (!wm.contains(newAssertion)) { System.out.println("Success: " + newAssertion); wm.addAssertion(newAssertion); // フラグをセット newAssertionCreated = true; } } } } System.out.println("Working Memory" + wm); } // 新しいアサーションが生成される限り do 内を繰り返す while (newAssertionCreated);
System.out.println("No rule produces a new assertion"); // ワーキングメモリの内容を返す
return wm.toString(); }
/**
* 後件に含まれる変数を束縛情報を利用して具体化するメソッド<br/> * ?x is made in Japan → my-car is made in Japan
* * @param thePattern * 後件 * @param theBindings * 束縛情報 * @return 具体化された後件 */
private String instantiate(String thePattern, HashMap theBindings) { String result = "";
// 後件をトークンに分割
StringTokenizer st = new StringTokenizer(thePattern); // トークンの数だけループ
for (int i = 0; i < st.countTokens();) { String tmp = st.nextToken(); // トークンが束縛情報に含まれる if (var(tmp)) {
// 変数なら束縛情報から文字列を取り出す
result = result + " " + (String) theBindings.get(tmp); } // それ以外はそのまま else { result = result + " " + tmp; } } return result.trim(); }
private boolean var(String str1) { // 先頭が ? なら変数
return str1.startsWith("?"); } /** * ルールを読み込むメソッド * * @param theFileName * ファイルの名前 */
private void loadRules(String theFileName) { String line; try { int token; f = new FileReader(theFileName); st = new StreamTokenizer(f); // ファイルを読み終わるまで続ける
while ((token = st.nextToken()) != StreamTokenizer.TT_EOF) { // 読み込んだものによって処理を変える switch (token) { // 文字 case StreamTokenizer.TT_WORD: // 各変数の初期化 String name = null;
ArrayList<String> antecedents = null; String consequent = null;
// 読み込んだ部分が"rule"のとき if ("rule".equals(st.sval)) { // 次に進める st.nextToken(); // ルールの名前を name に保存 name = st.sval; // 次に進める st.nextToken(); // 読み込んだ部分が"if"のとき if ("if".equals(st.sval)) {
antecedents = new ArrayList<String>(); st.nextToken(); // 前件は複数ある場合が あるので"then"と一致するまで保存する while (!"then".equals(st.sval)) { antecedents.add(st.sval); st.nextToken(); } // 同じように後件の保存 if ("then".equals(st.sval)) { st.nextToken(); consequent = st.sval; } } } // ルールの生成
rules.add(new Rule(name, antecedents, consequent)); break; // 文字以外(数字とか改行とか) default: System.out.println("エラー?" + token); break; } } } catch (Exception e) { System.out.println(e); }// ルールの表示
for (int i = 0; i < rules.size(); i++) {
System.out.println(rules.get(i).toString()); }
} } /** * ルールを表すクラス. */ class Rule { // 名前 String name; // 前件 ArrayList<String> antecedents; // 後件 String consequent; // コンストラクタ
Rule(String theName, ArrayList<String> theAntecedents, String theConsequent) { this.name = theName; this.antecedents = theAntecedents; this.consequent = theConsequent; } /** * ルールの名前を返す. * * @return 名前を表す String */
public String getName() { return name; } /** * ルールを String 形式で返す * * @return ルールを整形した String */
public String toString() {
return name + " " + antecedents.toString() + "->" + consequent; } /** * ルールの前件を返す. * * @return 前件を表す ArrayList */
public ArrayList<String> getAntecedents() { return antecedents; } /** * ルールの後件を返す. * * @return 後件を表す String */
public String getConsequent() { return consequent; } } class Matcher { StringTokenizer st1; StringTokenizer st2;
HashMap<String, String> vars;
Matcher() {
vars = new HashMap<String, String>(); }
/**
* 予め変数の束縛が決まっている場合はこっちを呼び出す *
* 比較するトークン1 * @param string2 * 比較するトークン2 * @param bindings * 事前にわかっている変数の束縛リスト * @return 照合成功かどうか */
public boolean matching(String string1, String string2, HashMap<String, String> bindings) { this.vars = bindings;
return matching(string1, string2); }
public boolean matching(String string1, String string2) { // 同じなら成功 if (string1.equals(string2)) return true; // 各々トークンに分ける st1 = new StringTokenizer(string1); st2 = new StringTokenizer(string2); // 数が異なったら失敗 if (st1.countTokens() != st2.countTokens()) return false; // 定数同士
for (int i = 0; i < st1.countTokens();) {
if (!tokenMatching(st1.nextToken(), st2.nextToken())) { // トークンが一つでもマッチングに失敗したら失敗 return false; } } // 最後まで O.K. なら成功 return true;
}
boolean tokenMatching(String token1, String token2) { if (token1.equals(token2))
return true;
if (var(token1) && !var(token2))
return varMatching(token1, token2); if (!var(token1) && var(token2))
return varMatching(token2, token1); return false;
}
boolean varMatching(String vartoken, String token) { if (vars.containsKey(vartoken)) { if (token.equals(vars.get(vartoken))) { return true; } else { return false; } } else { vars.put(vartoken, token); } return true; } boolean var(String str1) { // 先頭が ? なら変数 return str1.startsWith("?"); } }