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
+ }
0 commit comments