@@ -290,24 +290,23 @@ def is_definite_clause(s):
290
290
>>> is_definite_clause(expr('(Farmer(f) | Rabbit(r)) ==> Hates(f, r)'))
291
291
False
292
292
"""
293
- if is_symbol (s .op ): return True
294
- if s . op != '>>' : return False
295
- antecedent , consequent = s . args
296
- antecedent = NaryExpr ( '&' , antecedent )
297
- return (is_symbol (consequent .op )
298
- and (is_symbol (antecedent .op )
299
- or ( antecedent . op == '&'
300
- and all ( is_symbol ( arg . op ) for arg in antecedent . args ))))
293
+ if is_symbol (s .op ):
294
+ return True
295
+ elif s . op == '>>' :
296
+ antecedent , consequent = s . args
297
+ return (is_symbol (consequent .op )
298
+ and all (is_symbol (arg .op ) for arg in conjuncts ( antecedent )) )
299
+ else :
300
+ return False
301
301
302
302
def parse_definite_clause (s ):
303
303
"Return the antecedents and the consequent of a definite clause."
304
304
assert is_definite_clause (s )
305
305
if is_symbol (s .op ):
306
306
return [], s
307
- antecedent , consequent = s .args
308
- antecedent = NaryExpr ('&' , antecedent )
309
- antecedents = antecedent .args if antecedent .op == '&' else [antecedent ]
310
- return antecedents , consequent
307
+ else :
308
+ antecedent , consequent = s .args
309
+ return conjuncts (antecedent ), consequent
311
310
312
311
## Useful constant Exprs used in examples and code:
313
312
TRUE , FALSE , ZERO , ONE , TWO = map (Expr , ['TRUE' , 'FALSE' , 0 , 1 , 2 ])
@@ -462,8 +461,8 @@ def move_not_inwards(s):
462
461
NOT = lambda b : move_not_inwards (~ b )
463
462
a = s .args [0 ]
464
463
if a .op == '~' : return move_not_inwards (a .args [0 ]) # ~~A ==> A
465
- if a .op == '&' : return NaryExpr ('|' , * map (NOT , a .args ))
466
- if a .op == '|' : return NaryExpr ('&' , * map (NOT , a .args ))
464
+ if a .op == '&' : return associate ('|' , map (NOT , a .args ))
465
+ if a .op == '|' : return associate ('&' , map (NOT , a .args ))
467
466
return s
468
467
elif is_symbol (s .op ) or not s .args :
469
468
return s
@@ -477,7 +476,7 @@ def distribute_and_over_or(s):
477
476
((A | C) & (B | C))
478
477
"""
479
478
if s .op == '|' :
480
- s = NaryExpr ('|' , * s .args )
479
+ s = associate ('|' , s .args )
481
480
if s .op != '|' :
482
481
return distribute_and_over_or (s )
483
482
if len (s .args ) == 0 :
@@ -488,36 +487,43 @@ def distribute_and_over_or(s):
488
487
if not conj :
489
488
return s
490
489
others = [a for a in s .args if a is not conj ]
491
- rest = NaryExpr ('|' , * others )
492
- return NaryExpr ('&' , * [distribute_and_over_or (c | rest )
490
+ rest = associate ('|' , others )
491
+ return associate ('&' , [distribute_and_over_or (c | rest )
493
492
for c in conj .args ])
494
493
elif s .op == '&' :
495
- return NaryExpr ('&' , * map (distribute_and_over_or , s .args ))
494
+ return associate ('&' , map (distribute_and_over_or , s .args ))
496
495
else :
497
496
return s
498
497
499
- _NaryExprTable = {'&' :TRUE , '|' :FALSE , '+' :ZERO , '*' :ONE }
500
-
501
- def NaryExpr (op , * args ):
502
- """Create an Expr, but with an nary, associative op, so we can promote
503
- nested instances of the same op up to the top level.
504
- >>> NaryExpr('&', (A&B),(B|C),(B&C))
498
+ def associate (op , args ):
499
+ """Given an associative op, return an expression with the same
500
+ meaning as Expr(op, *args), but flattened -- that is, with nested
501
+ instances of the same op promoted to the top level.
502
+ >>> associate('&', [(A&B),(B|C),(B&C)])
505
503
(A & B & (B | C) & B & C)
506
- >>> NaryExpr ('|', A|(B|(C|(A&B))))
504
+ >>> associate ('|', [ A|(B|(C|(A&B)))] )
507
505
(A | B | C | (A & B))
508
506
"""
509
- arglist = []
507
+ args = disassociate (op , args )
508
+ if len (args ) == 0 :
509
+ return _op_identity [op ]
510
+ elif len (args ) == 1 :
511
+ return args [0 ]
512
+ else :
513
+ return Expr (op , * args )
514
+
515
+ _op_identity = {'&' :TRUE , '|' :FALSE , '+' :ZERO , '*' :ONE }
516
+
517
+ def disassociate (op , args ):
518
+ """Given an associative op, return a flattened list result such
519
+ that Expr(op, *result) means the same as Expr(op, *args)."""
520
+ result = []
510
521
def collect (subargs ):
511
522
for arg in subargs :
512
523
if arg .op == op : collect (arg .args )
513
- else : arglist .append (arg )
524
+ else : result .append (arg )
514
525
collect (args )
515
- if len (arglist ) == 1 :
516
- return arglist [0 ]
517
- elif len (arglist ) == 0 :
518
- return _NaryExprTable [op ]
519
- else :
520
- return Expr (op , * arglist )
526
+ return result
521
527
522
528
def conjuncts (s ):
523
529
"""Return a list of the conjuncts in the sentence s.
@@ -526,10 +532,7 @@ def conjuncts(s):
526
532
>>> conjuncts(A | B)
527
533
[(A | B)]
528
534
"""
529
- if isinstance (s , Expr ) and s .op == '&' :
530
- return s .args
531
- else :
532
- return [s ]
535
+ return disassociate ('&' , [s ])
533
536
534
537
def disjuncts (s ):
535
538
"""Return a list of the disjuncts in the sentence s.
@@ -538,10 +541,7 @@ def disjuncts(s):
538
541
>>> disjuncts(A & B)
539
542
[(A & B)]
540
543
"""
541
- if isinstance (s , Expr ) and s .op == '|' :
542
- return s .args
543
- else :
544
- return [s ]
544
+ return disassociate ('|' , [s ])
545
545
546
546
#______________________________________________________________________________
547
547
@@ -574,18 +574,19 @@ def pl_resolve(ci, cj):
574
574
if di == ~ dj or ~ di == dj :
575
575
dnew = unique (removeall (di , disjuncts (ci )) +
576
576
removeall (dj , disjuncts (cj )))
577
- clauses .append (NaryExpr ('|' , * dnew ))
577
+ clauses .append (associate ('|' , dnew ))
578
578
return clauses
579
579
580
580
#______________________________________________________________________________
581
581
582
582
class PropHornKB (PropKB ):
583
583
"A KB of propositional Horn clauses."
584
+ # Actually definite clauses, but I won't resolve this discrepancy till
585
+ # the code upgrade to the 3rd edition.
584
586
585
587
def tell (self , sentence ):
586
588
"Add a Horn clause to this KB."
587
- op = sentence .op
588
- assert op == '>>' or is_prop_symbol (op ), "Must be Horn clause" # XXX use is_definite_clause?
589
+ assert is_definite_clause (sentence ), "Must be definite clause"
589
590
self .clauses .append (sentence )
590
591
591
592
def ask_generator (self , query ):
@@ -594,23 +595,19 @@ def ask_generator(self, query):
594
595
yield {}
595
596
596
597
def retract (self , sentence ):
597
- "Remove the sentence's clauses from the KB"
598
- for c in conjuncts (to_cnf (sentence )):
599
- if c in self .clauses :
600
- self .clauses .remove (c )
598
+ self .clauses .remove (sentence )
601
599
602
600
def clauses_with_premise (self , p ):
603
601
"""Return a list of the clauses in KB that have p in their premise.
604
602
This could be cached away for O(1) speed, but we'll recompute it."""
605
603
return [c for c in self .clauses
606
- if c .op == '>>' and p in conjuncts (c .args [0 ])] # XXX use parse_definite_clause?
604
+ if c .op == '>>' and p in conjuncts (c .args [0 ])]
607
605
608
606
def pl_fc_entails (KB , q ):
609
607
"""Use forward chaining to see if a HornKB entails symbol q. [Fig. 7.14]
610
608
>>> pl_fc_entails(Fig[7,15], expr('Q'))
611
609
True
612
610
"""
613
- # XXX use parse_definite_clause?
614
611
count = dict ([(c , len (conjuncts (c .args [0 ]))) for c in KB .clauses
615
612
if c .op == '>>' ])
616
613
inferred = DefaultDict (False )
0 commit comments