package context.arch.intelligibility.expression; import java.util.Iterator; import java.util.List; /** * Convenience class to represent a Disjunction of Expressions taking the Disjunctive Normal Form (DNF). * Its children must be a {@link Conjunction} of {@link Parameters} (which are terminal literals). * @author Brian Y. Lim * @see Conjunction * @see Parameter */ public class DNF extends Disjunction<Reason> { private static final long serialVersionUID = -4894904209721540072L; /** Convenience variable to get a DNF wrapping the Unknown expression. */ public static final DNF UNKNOWN = new DNF(Unknown.singleton); public DNF() { super(); } /** * Create a DNF with reason added. * This can be used as a convenient way to wrap a reason in a DNF. * @param reason first reason to add */ public DNF(Reason reason) { super(); add(reason); } /** * Create a DNF with one reason with the literal added. * This can be used as a convenient way to wrap a reason literal in a DNF. * @param literal */ public DNF(Parameter<?> literal) { super(); add(new Reason(literal)); } /** * Convenience method to get the first literal. * This is useful for unwrapping DNF with only one literal value. * Would throw exception if DNF or first Reason is empty. * @return */ public Parameter<?> getFirstLiteral() { return get(0).get(0); } /** * Converts expression fully to Disjunctive Normal Form (DNF). * Flattens to at most 2-3 layers: Disjunction of (Conjunctions or NOT(terminal) or terminal Expressions) * @param expression * @return */ public static DNF toDNF(Expression expression) { Expression collapsed = DNF.toCollapsed(expression); Expression deMorganed = Negation.deMorgan(collapsed); DNF dnf = DNF.deMorganToDNF(deMorganed); return dnf; } /** * Convenience function to format Expression tree into Disjunctive Normal Form. * It assumes that the expression has been formatted with the de Morgan function, * such that all NOTs are only at the leaves. * * @param deMorganed * @return */ private static DNF deMorganToDNF(Expression deMorganed) { DNF traces = new DNF(); Reason trace = new Reason(); toDnfRecurse(deMorganed, trace, traces); return traces; } /** * Recursively adds to the Disjunction traces. * As it traverses the expression tree, it either adds to trace, or creates new traces * @param expression tree to walk * @param trace to add to trace if appending a conjunction * @param traces to add new Disjunctions to */ private static void toDnfRecurse(Expression expression, Reason trace, DNF traces) { if (expression instanceof Disjunction) { /* * Create a new trace for each child in the Disjunction, and adds to it */ Disjunction<?> list = (Disjunction<?>)expression; for (Expression childExp : list) { Reason childTrace = trace.clone(); // duplicate trace to create variants with OR branches toDnfRecurse(childExp, childTrace, traces); } } else if (expression instanceof Conjunction) { /* * Append to the Conjunction trace */ Conjunction<?> list = (Conjunction<?>)expression; for (Expression childExp : list) { toDnfRecurse(childExp, trace, traces); } } else { // assume terminal leaf; also assumes Negation is terminal trace.add((Parameter<?>) expression); if (!traces.contains(trace)) { // add only once traces.add(trace); } } } /** * Collapses Conjunctions and Disjunctions if they only have one child. * @param expression * @return */ @SuppressWarnings({ "unchecked" }) private static Expression toCollapsed(Expression expression) { if (expression instanceof List<?>) { List<Expression> list = (List<Expression>)expression; for (int i = 0; i < list.size(); i++) { Expression childExp = list.get(i); childExp = toCollapsed(childExp); list.set(i, childExp); } // purge of empty children Iterator<Expression> it = list.iterator(); while (it.hasNext()) { Expression childExp = it.next(); if (childExp == null) { it.remove(); } } if (list.isEmpty()) { return null; } else if (list.size() == 1) { return list.get(0); } // even if only 1 child, still return a conjunction else { return (Expression)list; } } else if (expression instanceof Negation) { Expression childExp = ((Negation) expression).getExpression(); // unwrap childExp = toCollapsed(childExp); // collapse child return Negation.negate(childExp); // wrap back } else { return expression; } } }