package aima.core.logic.propositional.kb.data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import aima.core.logic.propositional.parsing.ast.PropositionSymbol;
import aima.core.util.SetOps;
/**
* Artificial Intelligence A Modern Approach (3rd Edition): page 253.<br>
* <br>
* A Clause: A disjunction of literals. Here we view a Clause as a set of
* literals. This respects the restriction, under resolution, that a resulting
* clause should contain only 1 copy of a resulting literal. In addition,
* clauses, as implemented, are immutable.
*
*
* @author Ciaran O'Reilly
*
*/
public class Clause {
public static final Clause EMPTY = new Clause();
//
private Set<Literal> literals = new LinkedHashSet<Literal>();
//
private Set<PropositionSymbol> cachedPositiveSymbols = new LinkedHashSet<PropositionSymbol>();
private Set<PropositionSymbol> cachedNegativeSymbols = new LinkedHashSet<PropositionSymbol>();
private Set<PropositionSymbol> cachedSymbols = new LinkedHashSet<PropositionSymbol>();
//
private Boolean cachedIsTautologyResult = null;
private String cachedStringRep = null;
private int cachedHashCode = -1;
/**
* Default constructor - i.e. the empty clause, which is 'False'.
*/
public Clause() {
// i.e. the empty clause
this(new ArrayList<Literal>());
}
/**
* Construct a clause from the given literals. Note: literals the are always
* 'False' (i.e. False or ~True) are not added to the instantiated clause.
*
* @param literals
* the literals to be added to the clause.
*/
public Clause(Literal... literals) {
this(Arrays.asList(literals));
}
/**
* Construct a clause from the given literals. Note: literals the are always
* 'False' (i.e. False or ~True) are not added to the instantiated clause.
*
* @param literals
*/
public Clause(Collection<Literal> literals) {
for (Literal l : literals) {
if (l.isAlwaysFalse()) {
// Don't add literals of the form
// False | ~True
continue;
}
if (this.literals.add(l)) {
// Only add to caches if not already added
if (l.isPositiveLiteral()) {
this.cachedPositiveSymbols.add(l.getAtomicSentence());
} else {
this.cachedNegativeSymbols.add(l.getAtomicSentence());
}
}
}
cachedSymbols.addAll(cachedPositiveSymbols);
cachedSymbols.addAll(cachedNegativeSymbols);
// Make immutable
this.literals = Collections.unmodifiableSet(this.literals);
cachedSymbols = Collections.unmodifiableSet(cachedSymbols);
cachedPositiveSymbols = Collections
.unmodifiableSet(cachedPositiveSymbols);
cachedNegativeSymbols = Collections
.unmodifiableSet(cachedNegativeSymbols);
}
/**
* If a clause is empty - a disjunction of no disjuncts - it is equivalent
* to 'False' because a disjunction is true only if at least one of its
* disjuncts is true.
*
* @return true if an empty clause, false otherwise.
*/
public boolean isFalse() {
return isEmpty();
}
/**
*
* @return true if the clause is empty (i.e. 'False'), false otherwise.
*/
public boolean isEmpty() {
return literals.size() == 0;
}
/**
* Determine if a clause is unit, i.e. contains a single literal.
*
* @return true if the clause is unit, false otherwise.
*/
public boolean isUnitClause() {
return literals.size() == 1;
}
/**
* Determine if a definite clause. A definite clause is a disjunction of
* literals of which <i>exactly one is positive</i>. <q>For example, the
* clause (¬L<sub>1,1</sub> ∨ ¬Breeze ∨ B<sub>1,1</sub>) is a
* definite clause, whereas (¬B<sub>1,1</sub> ∨ P<sub>1,2</sub> ∨
* P<sub>2,1</sub>) is not.</q>
*
*
* @return true if a definite clause, false otherwise.
*/
public boolean isDefiniteClause() {
return cachedPositiveSymbols.size() == 1;
}
/**
* Determine if an implication definite clause. An implication definite
* clause is disjunction of literals of which exactly 1 is positive and
* there is 1 or more negative literals.
*
* @return true if an implication definite clause, false otherwise.
*/
public boolean isImplicationDefiniteClause() {
return isDefiniteClause() && cachedNegativeSymbols.size() >= 1;
}
/**
* Determine if a Horn clause. A horn clause is a disjunction of literals of
* which <i>at most one is positive</i>.
*
* @return true if a Horn clause, false otherwise.
*/
public boolean isHornClause() {
return !isEmpty() && cachedPositiveSymbols.size() <= 1;
}
/**
* Clauses with no positive literals are called <b>goal clauses</b>.
*
* @return true if a Goal clause, false otherwise.
*/
public boolean isGoalClause() {
return !isEmpty() && cachedPositiveSymbols.size() == 0;
}
/**
* Determine if the clause represents a tautology, of which the following
* are examples:<br>
*
* <pre>
* {..., True, ...}
* {..., ~False, ...}
* {..., P, ..., ~P, ...}
* </pre>
*
* @return true if the clause represents a tautology, false otherwise.
*/
public boolean isTautology() {
if (cachedIsTautologyResult == null) {
for (Literal l : literals) {
if (l.isAlwaysTrue()) {
// {..., True, ...} is a tautology.
// {..., ~False, ...} is a tautology
cachedIsTautologyResult = true;
}
}
// If we still don't know
if (cachedIsTautologyResult == null) {
if (SetOps.intersection(cachedPositiveSymbols, cachedNegativeSymbols)
.size() > 0) {
// We have:
// P | ~P
// which is always true.
cachedIsTautologyResult = true;
}
else {
cachedIsTautologyResult = false;
}
}
}
return cachedIsTautologyResult;
}
/**
*
* @return the number of literals contained by the clause.
*/
public int getNumberLiterals() {
return literals.size();
}
/**
*
* @return the number of positive literals contained by the clause.
*/
public int getNumberPositiveLiterals() {
return cachedPositiveSymbols.size();
}
/**
*
* @return the number of negative literals contained by the clause.
*/
public int getNumberNegativeLiterals() {
return cachedNegativeSymbols.size();
}
/**
*
* @return the set of literals making up the clause.
*/
public Set<Literal> getLiterals() {
return literals;
}
/**
*
* @return the set of symbols from the clause's positive and negative literals.
*/
public Set<PropositionSymbol> getSymbols() {
return cachedSymbols;
}
/**
*
* @return the set of symbols from the clause's positive literals.
*/
public Set<PropositionSymbol> getPositiveSymbols() {
return cachedPositiveSymbols;
}
/**
*
* @return the set of symbols from the clause's negative literals.
*/
public Set<PropositionSymbol> getNegativeSymbols() {
return cachedNegativeSymbols;
}
@Override
public String toString() {
if (cachedStringRep == null) {
StringBuilder sb = new StringBuilder();
boolean first = true;
sb.append("{");
for (Literal l : literals) {
if (first) {
first = false;
} else {
sb.append(", ");
}
sb.append(l);
}
sb.append("}");
cachedStringRep = sb.toString();
}
return cachedStringRep;
}
@Override
public boolean equals(Object othObj) {
if (null == othObj) {
return false;
}
if (this == othObj) {
return true;
}
if (!(othObj instanceof Clause)) {
return false;
}
Clause othClause = (Clause) othObj;
return othClause.literals.equals(this.literals);
}
@Override
public int hashCode() {
if (cachedHashCode == -1) {
cachedHashCode = literals.hashCode();
}
return cachedHashCode;
}
}