Skip to content

Commit ed87a2d

Browse files
Initial commit.
0 parents  commit ed87a2d

File tree

4 files changed

+249
-0
lines changed

4 files changed

+249
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
npm-debug.log*
2+
node_modules

app.js

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
const readline = require('readline-sync');
2+
const kb = require('./kb-plants.js');
3+
const verbose = false;
4+
5+
const forwardChain = function(assertions) {
6+
// Select the first rule.
7+
let ruleIndex = 0;
8+
let rule = kb[ruleIndex];
9+
10+
// While there are more rules.
11+
while (ruleIndex < kb.length) {
12+
// If all premises in the rule are in assertions
13+
const allPremisesExist = rule.premises.every(premise =>
14+
assertions.some(assertion => assertion.attribute === premise.attribute && assertion.value === premise.value)
15+
);
16+
17+
// If all premises from the rule are in the assertions but not the conclusion.
18+
if (allPremisesExist && !assertions.some(assertion => assertion.attribute === rule.conclusion.attribute && assertion.value === rule.conclusion.value)) {
19+
// Add the conclusion to assertions.
20+
assertions.push(rule.conclusion);
21+
22+
// Go back to the first rule, since the new assertion might exist as a premise in another rule.
23+
// This can be optimized by sorting rules based on if A's conclusion is a premise in B's, then place A before B.
24+
ruleIndex = 0;
25+
}
26+
else {
27+
// Select the next rule.
28+
rule = kb[++ruleIndex];
29+
}
30+
}
31+
32+
return assertions;
33+
};
34+
35+
const backChain = function(goal, assertions) {
36+
// Select the first rule.
37+
let ruleIndex = 0;
38+
assertions = assertions || [ goal ];
39+
40+
// If there is an assertion with goal as its attribute.
41+
verbose && console.log(`Current goal: ${JSON.stringify(goal)}`);
42+
verbose && console.log(`Current assertions: ${JSON.stringify(assertions)}`);
43+
let assertion = assertions.filter(assertion => assertion.attribute === goal.attribute && assertion.value);
44+
assertion = assertion.length ? assertion[0] : null;
45+
verbose && console.log(`Found assertion: ${JSON.stringify(assertion)}`);
46+
47+
if (!assertion) {
48+
// Go back to the first rule.
49+
ruleIndex = 0;
50+
51+
while (!assertion && ruleIndex < kb.length) {
52+
const rule = kb[ruleIndex];
53+
54+
if (rule.conclusion.attribute === goal.attribute) {
55+
let premiseIndex = 0;
56+
let allPremisesTrue = false;
57+
let premise = rule.premises[premiseIndex];
58+
let isPremiseAssertionTrue = true;
59+
60+
verbose && console.log(`Rule conclusion: ${JSON.stringify(rule.conclusion)}`);
61+
62+
while (!allPremisesTrue && isPremiseAssertionTrue) {
63+
const nextGoal = premise;
64+
65+
verbose && console.log(`Calling backChain for goal ${JSON.stringify(nextGoal)}`);
66+
trueAssertion = backChain(nextGoal, assertions);
67+
verbose && console.log(`trueAssertion: ${JSON.stringify(trueAssertion)}`);
68+
69+
// Add the assertion to the assertion list.
70+
assertions.push(trueAssertion);
71+
72+
verbose && console.log(`${JSON.stringify(premise)} === ${JSON.stringify(trueAssertion)}`)
73+
74+
// Is the trueAssertion equal to the premise?
75+
isPremiseAssertionTrue = JSON.stringify(premise) === JSON.stringify(trueAssertion);
76+
if (isPremiseAssertionTrue) {
77+
verbose && console.log(`${premiseIndex + 1} < ${rule.premises.length}`);
78+
79+
if (++premiseIndex < rule.premises.length) {
80+
premise = rule.premises[premiseIndex];
81+
82+
verbose && console.log(allPremisesTrue);
83+
verbose && console.log(`Using new premise ${JSON.stringify(premise)}`);
84+
}
85+
else {
86+
verbose && console.log('All premises true!');
87+
allPremisesTrue = true;
88+
}
89+
}
90+
}
91+
92+
if (allPremisesTrue) {
93+
verbose && console.log(`New assertion: ${JSON.stringify(rule.conclusion)}`);
94+
assertion = rule.conclusion;
95+
}
96+
}
97+
98+
// Select the next rule.
99+
ruleIndex++;
100+
}
101+
102+
if (!assertion) {
103+
let existingAssertion = assertions.filter(a => a.attribute === goal.attribute);
104+
existingAssertion = existingAssertion.length ? existingAssertion[0] : null;
105+
if (existingAssertion) {
106+
assertion = existingAssertion;
107+
}
108+
else {
109+
// Prompt user for goal.
110+
const value = readline.question(`What is the value for ${goal.attribute}? `);
111+
assertion = { attribute: goal.attribute, value }; // Enter new assertion for the type and value specified by the user.
112+
}
113+
}
114+
else {
115+
const index = assertions.indexOf(a => a.attribute === assertion.attribute && !a.value);
116+
if (index > -1) {
117+
verbose && console.log('Updating value!');
118+
assertions[index].value = assertion.value;
119+
}
120+
}
121+
}
122+
123+
return assertion;
124+
};
125+
126+
127+
//
128+
// Test forward-chaining.
129+
// User inputs: stem woody, position upright, one main trunk yes, broad and flat no
130+
// Output includes: type is tree (because of original inputs), then using type is tree we can now deduce class gymnosperm
131+
//
132+
let assertions = [];
133+
let attribute = 1;
134+
while (attribute) {
135+
attribute = readline.question('Enter an attribute? ');
136+
if (attribute) {
137+
let value = readline.question('Enter a value? ');
138+
value && assertions.push({ attribute, value });
139+
}
140+
}
141+
142+
assertions = forwardChain(assertions);
143+
console.log(assertions);
144+
145+
//
146+
// Test backward-chaining.
147+
// User inputs: class, stem woody, position upright, one main trunk yes, broad and flat yes
148+
// Output: angiosperm
149+
//
150+
let goal = { attribute: readline.question('What attribute type do you want to know? ') };
151+
assertion = backChain(goal);
152+
153+
if (assertion.value) {
154+
console.log(assertion.value);
155+
}
156+
else {
157+
console.log(`You cannot answer enough questions to determine ${goal.attribute}.`);
158+
}

kb-plants.js

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const KB = [
2+
{
3+
premises: [
4+
{ attribute: 'class', value: 'gymnosperm' },
5+
{ attribute: 'leaf shape', value: 'scalelike' }
6+
],
7+
conclusion: { attribute: 'family', value: 'cypress' }
8+
},
9+
{
10+
premises: [
11+
{ attribute: 'class', value: 'gymnosperm' },
12+
{ attribute: 'leaf shape', value: 'needlelike' },
13+
{ attribute: 'pattern', value: 'random' }
14+
],
15+
conclusion: { attribute: 'family', value: 'pine' }
16+
},
17+
{
18+
premises: [
19+
{ attribute: 'class', value: 'gymnosperm' },
20+
{ attribute: 'leaf shape', value: 'needlelike' },
21+
{ attribute: 'pattern', value: 'two even lines' },
22+
{ attribute: 'silvery band', value: 'yes' }
23+
],
24+
conclusion: { attribute: 'family', value: 'pine' }
25+
},
26+
{
27+
premises: [
28+
{ attribute: 'class', value: 'gymnosperm' },
29+
{ attribute: 'leaf shape', value: 'needlelike' },
30+
{ attribute: 'pattern', value: 'two even lines' },
31+
{ attribute: 'silvery band', value: 'no' }
32+
],
33+
conclusion: { attribute: 'family', value: 'bald cypress' }
34+
},
35+
{
36+
premises: [
37+
{ attribute: 'type', value: 'tree' },
38+
{ attribute: 'broad and flat', value: 'yes' }
39+
],
40+
conclusion: { attribute: 'class', value: 'angiosperm' }
41+
},
42+
{
43+
premises: [
44+
{ attribute: 'type', value: 'tree' },
45+
{ attribute: 'broad and flat', value: 'no' }
46+
],
47+
conclusion: { attribute: 'class', value: 'gymnosperm' }
48+
},
49+
{
50+
premises: [
51+
{ attribute: 'stem', value: 'green' },
52+
],
53+
conclusion: { attribute: 'type', value: 'herb' }
54+
},
55+
{
56+
premises: [
57+
{ attribute: 'stem', value: 'woody' },
58+
{ attribute: 'position', value: 'creeping' },
59+
],
60+
conclusion: { attribute: 'type', value: 'vine' }
61+
},
62+
{
63+
premises: [
64+
{ attribute: 'stem', value: 'woody' },
65+
{ attribute: 'position', value: 'upright' },
66+
{ attribute: 'one main trunk', value: 'yes' }
67+
],
68+
conclusion: { attribute: 'type', value: 'tree' }
69+
},
70+
{
71+
premises: [
72+
{ attribute: 'stem', value: 'woody' },
73+
{ attribute: 'position', value: 'upright' },
74+
{ attribute: 'one main trunk', value: 'no' }
75+
],
76+
conclusion: { attribute: 'type', value: 'shrub' }
77+
},
78+
79+
];
80+
81+
module.exports = KB;

package.json

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "knowledge-base-test",
3+
"version": "0.0.1",
4+
"main": "app.js",
5+
"dependencies": {
6+
"readline-sync": "*"
7+
}
8+
}

0 commit comments

Comments
 (0)