package finiteAutomata; import java.util.Vector; /** * Chomsky Normal Form grammar. Every context-free grammar can be represented * by an equivalent CNF grammar. * *

A CNF grammar has a non-empty set of variables, * a non-empty set of terminal symbols (disjoint from the set of variables), * and a set of production rules for transforming the variables. One of the * variables is singled out as the "start" variable. * *

Data Fields: The terminal symbols of a CNF object are stored as * an Alphabet, "alpha". The variables are denoted v0, v1, v2, etc., where * v0 is always the start variable. "numVars" records the number of variables * in the grammar. * *

The grammar rules are stored in a two-dimensional array (numVars x 2) * of Vectors of CNFRule objects. If vn is one of the variables, all the * type 0 rules for transforming vn (form "vn -> vi vj" are stored in the * Vector "rule[n][0]", and all the type 1 rules for transforming vn * (form "vn -> terminal") are stored in the Vector "rule[n][1]". * *

If the rule "v0 -> epsilon" is included in the grammar, the boolean * variable "startToEpsilon" is set to true (this is the only rule not * stored in the rule array). * * @author Barbara Wahl * @version Fall 2007 */ public class CNF { // ** DATA FIELDS ** /** * terminal symbols */ protected Alphabet alpha; /** * number of variables */ protected int numVars; /** * true if "v0 -> epsilon" is a rule */ protected boolean startToEpsilon; /** * array of Vectors of CNFRule objects; rule[i][j] contains * all the type j rules which transform vi */ protected Vector[][] rule; // ** CONSTRUCTORS ** /** * 0-parameter constructor, prompts the user for CNF information. * @post constructs a CNF grammar with all data fields set according * to user input */ public CNF() { int choice; // predefined = true if user wants to use a predefined CNF object boolean predefined = queryPredefined(); if(predefined) predefinedDialog(); else { // "alpha" & "numVars" for user-defined CNF numVars = queryNumVars(); alpha = new Alphabet(queryAlphabet()); startToEpsilon = false; // allocate 2-dim'l array to hold references to Vectors rule = new Vector[numVars][2]; for(int i=0; i epsilon" is to be a rule * @param r 2-dimensional array of Vectors of CNFRule objects * @pre a is a non-empty string with no repeated characters * @pre n > 0 * @pre r is size n x 2, and for each 0 <= i < n, r[i][j] is a * non-null Vector (possibly empty) of CNFRule objects of type j * which transform the variable vi (j = 0, 1) * @post constructs a CNF grammar with Alphabet a, n variables, * with rules given by the array r (and v0 -> epsilon iff b == true); * the data field "rule" simply references r (r is not copied) */ public CNF(String a, int n, boolean b, Vector[][] r) { Assert.pre(n > 0, "n is positive"); alpha = new Alphabet(a); numVars = n; startToEpsilon = b; rule = new Vector[n][2]; for(int i=0; i t" to the rule set. * @param n integer specifying LHS variable * @param t char specifying terminal symbol for RHS * @pre n is a valid variable number for this CNF * @pre t is a valid terminal symbol for this CNF * @post if preconditions are met, and if the rule does not currently * exist, the rule "vn -> t" is added to this CNF's rule set; * otherwise, warning message is printed */ public void addTypeOne(int n, char t) { if(!this.isValidVariable(n) || !alpha.contains(t)) { System.out.println("Invalid rule not added; can't have "); System.out.print(new CNFRule(n,t)); return; } // check if this rule already exists CNFRule newRule = new CNFRule(n,t); if(rule[n][1].contains(newRule)) System.out.println("Rule " + newRule + " was not added (it already exists)."); // proceed to add this rule else { rule[n][1].add(newRule); System.out.println("The rule " + newRule + " +" + "has been added."); } } /** * Adds rule "vn -> vi vj" to the rule set. * @param n integer specifying LHS variable * @param i integer specifying first RHS variable * @param j integer specifying second RHS variable * @pre n, i, and j are valid variable numbers for this CNF * @post if preconditions are met, if i>0 and j>0, and if * the rule does not currently exist, rule "vn -> vi vj" * is added to this CNF's rule set; otherwise, warning * message is printed */ public void addTypeZero(int n, int i, int j) { if(!this.isValidVariable(n) || !this.isValidVariable(i) || !this.isValidVariable(j) || i == 0 || j == 0) { System.out.println("Invalid rule not added; " + "can't have "); System.out.print(new CNFRule(n,i,j)); return; } // check if this rule already exists CNFRule newRule = new CNFRule(n,i,j); if(rule[n][0].contains(newRule)) System.out.println("Rule " + newRule + " was not added (it already exists)."); // proceed to add this rule else { rule[n][0].add(newRule); System.out.println("The rule " + newRule + " +" + "has been added."); } } /** * Returns all non-empty terminal strings derivable in a given number * of steps. * @param maxSteps int upper bound on derivation length * @return Vector containing all NON-EMPTY terminal strings derivable * in maxSteps or fewer steps from start; strings with multiple * derivations WILL appear multiple times */ public Vector getLanguage(int maxSteps) { Vector q = new Vector(); // working space (use as queue) q.add(new StringVarTerm(alpha)); // enqueue the start string, "v0" Vector result = new Vector(); // store strings for return StringVarTerm current = (StringVarTerm) q.elementAt(0); StringVarTerm newString; CNFRule r; int leftMostIndex; // position of left-most variable in current int lmv; // left-most variable in current int head = 0; // index for top-of-queue (for lazy deletion) // PLAN: current = q.dequeue; while current.numSteps isn't too // large, either store current (if it has no vars) or use current // to generate more strings (enqueue the new strings at tail of // q & repeat) while(current.numSteps() <= maxSteps) { head++; // effectively, q.dequeue if(current.vars() == 0) // current is all terminals -- store it! result.add(current); // Otherwise, apply each applicable rule to "current" & // enequeue results (FIRST check current.numSteps < maxSteps) else if(current.numSteps() < maxSteps) { leftMostIndex = current.leftMostVarIndex(); lmv = current.decodeVar(leftMostIndex); // code to prevent useless application of type 0 rules int jStart = 0; if(current.numSteps() + current.vars() >= maxSteps) jStart = 1; // for each rule which applies to left-most-variable "lmv"... for(int j = jStart; j<2; j++) // j = rule type for(int i=0; i= q.size()) break; // dequeue to current and repeat until maxSteps exceeded current = (StringVarTerm)q.elementAt(head); } return result; } /** * Returns all non-empty terminal strings derivable in a given number * of steps, without repetitions. * @param maxSteps int upper bound on derivation length * @return Vector containing all NON-EMPTY terminal strings derivable * in maxSteps or fewer steps from start; strings with multiple * derivations will NOT appear multiple times */ public Vector getLanguageUnambiguous(int maxSteps) { Vector q = new Vector(); // working space (use as queue) q.add(new StringVarTerm(alpha)); // enqueue the start string, "v0" Vector result = new Vector(); // store terminal strings for return StringVarTerm current = (StringVarTerm) q.elementAt(0); StringVarTerm newString; CNFRule r; int leftMostIndex; // position of left-most variable in current int lmv; // left-most variable in current int head = 0; // index for top-of-queue (for lazy deletion) Hashtable used = new Hashtable(9973); // creates a hashtable with // tableSize = 9973 (lg prime) String key; // will use String objects as keys // PLAN: current = q.dequeue; while current.numSteps is not too // large, either store current (if it has no vars) or use current // to generate more strings (enqueue the new strings at tail of // q & repeat). We avoid repetitions as follows. // First, each time an element is added to "q", it is also put into // the hashtable "used". Second, if an element "newString" // representsthe same string of variables and terminals (maybe with // a different derivation) as any element already in hashtable // "used", then "newString" will not be added to "q". while(current.numSteps() <= maxSteps) { head++; // effectively, q.dequeue if(current.vars() == 0) // current is all terminals; store result.add(current); // Otherwise, apply each applicable rule to "current" & // enqueue results (check current.numSteps < maxSteps) else if(current.numSteps() < maxSteps) { leftMostIndex = current.leftMostVarIndex(); lmv = current.decodeVar(leftMostIndex); // prevent useless application of type 0 rules int jStart = 0; if(current.numSteps() + current.vars() >= maxSteps) jStart = 1; // for each rule applicable to "lmv"... for(int j = jStart; j<2; j++) // j = rule type for(int i=0; i= q.size()) break; // stop if q is (virtually) empty // or, dequeue to current and repeat current = (StringVarTerm)q.elementAt(head); } System.out.println("Hashtable max load factor = " + used.loadFactor()); return result; } /** * Tests whether a given integer is a valid variable number. * number for this CNF * @param i int variable number to be tested * @return true iff i >= 0 and i < number of variables in this CNF */ public boolean isValidVariable(int i) { return (i >= 0 && i < numVars); } /** * Reports number of variables, alphabet of terminal symbols, and * rule set. */ public void printCNF() { System.out.println("numVars = " + numVars); System.out.println("alphabet of terminals = " + alpha.toPrettyString()); System.out.println("grammar rules:"); printRules(); } /** * Pretty-prints the rule set. */ public void printRules() { CNFRule theRule; if(startToEpsilon) System.out.println("v0 -> epsilon"); for(int n = 0; n < numVars; n++) // for each LHS variable n for(int t = 0; t < 2; t++) // for both rule types t // iterate thru rule[n][t] for(int i = 0; i < rule[n][t].size(); i++) { theRule = (CNFRule)rule[n][t].elementAt(i); System.out.println(theRule); } } /** * Removes rule "vn -> t" from the rule set. * @param n integer specifying LHS variable * @param t char specifying terminal symbol for RHS * @post if rule is present, removes rule "vn -> t" from * this CNF's rule set and informs user of deletion */ public void removeTypeOne(int n, char t) { if(this.isValidVariable(n) && alpha.contains(t)) rule[n][1].remove(new CNFRule(n,t)); System.out.println("The rule " + new CNFRule(n,t) + " has been removed."); } /** * Removes rule "vn -> vi vj" from the rule set. * @param n integer specifying LHS variable * @param i integer specifying first RHS variable * @param j integer specifying second RHS variable * @pre n, i, and j are valid variable numbers for this CNF * @post if rule is present, removes rule "vn -> vi vj" from * this CNF's rule set and informs user of deletion */ public void removeTypeZero(int n, int i, int j) { if(this.isValidVariable(n) && this.isValidVariable(i) && this.isValidVariable(j) && i > 0 && j > 0) rule[n][0].remove(new CNFRule(n,i,j)); System.out.println("The rule " + new CNFRule(n,i,j) + " has been removed."); } /** * Tests whether a given String is a terminal string which can be * generated by this CNF. * @param s String to be tested * @return the StringVarTerm object which shows how s is generated, * or "null" if s is not in the language of this CNF grammar */ public StringVarTerm testString(String s) { int n = s.length(); if(n==0 || !alpha.indexArray(s,new int[n])) return null; // give up if s not valid argument // generate all strings up through length n using this CNF grammar // Theorem: if s is a string of length n>0 in the language of // of a CNF grammar, then every derivation of s has length 2*n-1. Vector language = this.getLanguageUnambiguous(2*n-1); // look for s among the StringVarTerm objects in lang StringVarTerm current; for(int i=0; i> Enter a string with no spaces or " + "repeated characters (terminal symbols): "); return Util.readString(); } /** * Prints constructor menu header and queries user regarding number * of variables. * @return positive integer number of variables as specified by user */ protected int queryNumVars() { int num = 0; // temp storage for integer input System.out.println("****** CNF CONSTRUCTOR MENU ******"); // ask user how many variables; only accept num > 0 while(num < 1) { System.out.print(">> Enter a positive integer " + "(number of variables): "); num = Util.readNum(); } return num; } /** * Asks user if they want to have strings with ambiguous derivations * be repeated. * @return true iff user response begins with 'Y' or 'y' */ protected boolean queryAmbiguous() { System.out.print("Repeat strings with ambiguous derivations? " + "Enter Y or N: "); return Util.answerIsYes(); } /** * Repeatedly displays constructor menu options until user makes * a valid choice. * @return int choice number from 1 to 6 */ protected int constructorMenu() { int choice = -1; while(choice < 1 || choice > 6) { System.out.println("\nChoose an action: "); System.out.println(" 1 = add/remove a rule"); System.out.println(" 2 = print this CNF grammar"); System.out.println(" 3 = print the language of this" + " CNF grammar"); System.out.println(" 4 = test a non-empty string"); System.out.println(" 5 = test the epsilon string"); System.out.println(" 6 = DONE with this CNF grammar"); choice = Util.readNum(); } return choice; } /** * Takes the appropriate action based on constructorMenu options. * @param choice integer code for user's desired action * @pre 1 <= choice <= 5 */ protected void menuHandler(int choice) { Assert.pre(1 <= choice && choice <= 5, "1 <= choice <= 5"); switch (choice) { case 5: // test epsilon string if(this.startToEpsilon) System.out.println("epsilon is accepted"); else System.out.println("epsilon is rejected"); break; case 4: // test a non-epsilon string testStringDialog(); break; case 3: // print language printLanguageDialog(); break; case 2: // print CNF printCNF(); break; case 1: // add/remove a rule ruleChangeDialog(); } } /** * Asks user which type of rule change they want to make * and carries out that request. * @post rule array is altered to reflect addition/deletion * of a rule, or value of startToEpsilon is possibly changed */ protected void ruleChangeDialog() { int n = -1; while(n<0 || n>5) { System.out.println("Choose an option: "); System.out.println(" 0 = add a rule, form 0, " + "vn -> vi vj"); System.out.println(" 1 = add a rule, form 1, " + "vn -> terminal"); System.out.println(" 2 = add a rule, form 2, " + "start -> epsilon"); System.out.println(" 3 = remove a rule, form 0, " + "vn -> vi vj"); System.out.println(" 4 = remove a rule, form 1, " + "vn -> terminal"); System.out.println(" 5 = remove a rule, form 2, " + "start -> epsilon"); n = Util.readNum(); } switch(n) { case 5: // remove rule type 2, start -> epsilon startToEpsilon = false; System.out.println("The rule v0 -> epsilon " + "has been removed."); break; case 4: // remove rule type 1, vn -> terminal removeTypeOneDialog(); break; case 3: // remove rule type 0, vn -> vi vj removeTypeZeroDialog(); break; case 2: // add rule type 2, start -> epsilon startToEpsilon = true; System.out.println("The rule v0 -> epsilon " + "has been added."); break; case 1: // add rule type 1, vn -> terminal addTypeOneDialog(); break; case 0: // add rule type 0, vn -> vi vj addTypeZeroDialog(); } } /** * Queries user for n, i, and j in the rule "vn -> vi vj" and adds * the correpsonding rule. * @post rule array is updated */ protected void addTypeZeroDialog() { int n=-1, i=-1, j=-1; System.out.println("Adding rule of type 0, form vn -> vi vj."); while(n<0 || n>=numVars) { System.out.print("Enter value for n, the LHS variable:" + " n = "); n = Util.readNum(); } while(i<1 || i>=numVars) { System.out.print("Enter value for i, the first " + "RHS variable: i = "); i = Util.readNum(); } while(j<1 || j>=numVars) { System.out.print("Enter value for j, the second " + "RHS variable: j = "); j = Util.readNum(); } this.addTypeZero(n,i,j); } /** * Queries user for n and t in the rule "vn -> t" and adds * the correpsonding rule. * @post rule array is updated */ protected void addTypeOneDialog() { int n = -1; char t = ' '; System.out.println("Adding rule of type 1, form vn -> t."); while(n<0 || n>=numVars) { System.out.print("Enter value for n, the LHS variable: " + "n = "); n = Util.readNum(); } while(!alpha.contains(t)) { System.out.print("Enter a terminal symbol for RHS: t = "); // assume terminal = first char from the input string t = Util.readString().charAt(0); } this.addTypeOne(n,t); } /** * Queries user for n, i, and j, and removes rule "vn -> vi vj". * @post rule array is updated */ protected void removeTypeZeroDialog() { int n=-1, i=-1, j=-1; System.out.println("Removing rule of type 0, form vn -> vi vj."); while(n<0 || n>=numVars) { System.out.print("Enter value for n, the LHS " + "variable: n = "); n = Util.readNum(); } while(i<1 || i>=numVars) { System.out.print("Enter value for i, the first RHS " + "variable: i = "); i = Util.readNum(); } while(j<1 || j>=numVars) { System.out.print("Enter value for j, the second RHS " + "variable: j = "); j = Util.readNum(); } this.removeTypeZero(n,i,j); } /** * Queries user for n and t, and removes rule "vn -> t". * @post rule array is updated */ protected void removeTypeOneDialog() { int n = -1; char t = ' '; System.out.println("Removing rule of type 1, form vn -> t."); while(n<0 || n>=numVars) { System.out.print("Enter value for n, the LHS variable: " + "n = "); n = Util.readNum(); } while(!alpha.contains(t)) { System.out.print("Enter a terminal symbol for RHS: t = "); // assume terminal = first char from the input string t = Util.readString().charAt(0); } this.removeTypeOne(n,t); } /** * Queries user for max string length and prints this CNF's language. * @post all terminal strings generated by this CNF, up to the * user-specified max length, are printed; according to user * input, derivations may also be printed and strings with ambiguous * derivations may appear multiple times */ protected void printLanguageDialog() { int n = -1; while(n<=0) { System.out.print("Enter a positive value for n " + "(max derivation length): "); n = Util.readNum(); } boolean printDeriv = false; System.out.print("Enter YES if you want to also print " + "the derivations: "); String inString = Util.readString(); if(inString.charAt(0) == 'y' || inString.charAt(0) == 'Y') printDeriv = true; Vector lang; // ask whether to repeat strings with ambiguous derivations boolean ambiguous = queryAmbiguous(); if(ambiguous) lang = getLanguage(n); else lang = getLanguageUnambiguous(n); this.printLanguage(lang, printDeriv); } /** * Determines whether a user-specified String is generated by this grammar. * @post reports the result of the test and, if accepted, a derivation * of the string */ protected void testStringDialog() { String inString; System.out.print("Enter a string to test: "); inString = Util.readString(); StringVarTerm result = testString(inString); if(result!=null) { System.out.print(inString + " is accepted!"); System.out.print("\nDerivation: "); result.printDerivation(); } else System.out.println(inString + " is rejected"); } // ** OTHER METHODS ** /** * Prints the strings in the given Vector, preceded by "epsilon" if * epsilon is generated by this CNF. * @param v Vector representing the non-epsilon terminal strings * generated by this CNF (up to some maximum derivation length) * @param printDeriv determines whether the derivation of each * string is also printed * @pre v is a non-null Vector of non-null StringVarTerm objects * @post if v is empty and if epsilon is not generated, reports that * "the language is empty" */ protected void printLanguage(Vector v, boolean printDeriv) { StringVarTerm s; if(v.isEmpty() && !startToEpsilon) // empty language { System.out.println("Language is empty!"); return; } System.out.println("\nLanguage strings:"); if(this.startToEpsilon) { System.out.print("epsilon"); if(printDeriv) System.out.print(": v0 -> epsilon"); System.out.println(); } // iterate thru v, printing each string for(int i=0; i epsilon not a rule rule = new Vector[numVars][2]; // array of Vectors for rules // Create the rule Vectors for(int i=0; i v3 v4 rule[0][1].add(new CNFRule(0,'#')); // v0 -> # rule[1][0].add(new CNFRule(1,3,4)); // v1 -> v3 v4 rule[1][1].add(new CNFRule(1,'#')); // v1 -> # rule[2][1].add(new CNFRule(2,'1')); // v2 -> 1 rule[3][1].add(new CNFRule(3,'0')); // v3 -> 0 rule[4][0].add(new CNFRule(4,1,2)); // v4 -> v1 v2 } /** * Initializes this to be a CNF grammar equivalent to Sipser's G3 (p.103). * @post data fields of this CNF grammar are set accordingly */ protected void constructC2() { alpha = new Alphabet("ab"); // alphabet symbols numVars = 5; // numVars startToEpsilon = true; // start -> epsilon is a rule rule = new Vector[numVars][2]; // array of Vectors for rules // Create the rule Vectors for(int i=0; i v3 v2 rule[0][0].add(new CNFRule(0,1,1)); // v0 -> v1 v1 rule[0][0].add(new CNFRule(0,3,4)); // v0 -> v3 v4 rule[1][0].add(new CNFRule(1,3,2)); // v1 -> v3 v2 rule[1][0].add(new CNFRule(1,1,1)); // v1 -> v1 v1 rule[1][0].add(new CNFRule(1,3,4)); // v1 -> v3 v4 rule[2][0].add(new CNFRule(2,1,4)); // v2 -> v1 v4 rule[3][1].add(new CNFRule(3,'a')); // v3 -> a rule[4][1].add(new CNFRule(4,'b')); // v4 -> b } /** * Initializes this to be a CNF grammar equivalent to Sipser's G6 (p.108). * @post data fields of this CNF grammar are set accordingly */ protected void constructC3() { alpha = new Alphabet("ab"); // alphabet symbols numVars = 6; // numVars startToEpsilon = false; // start -> epsilon not a rule rule = new Vector[numVars][2]; // array of Vectors for rules // Create the rule Vectors for(int i=0; i a rule[0][0].add(new CNFRule(0,1,2)); // v0 -> v1 v2 rule[0][0].add(new CNFRule(0,4,5)); // v0 -> v4 v5 rule[0][0].add(new CNFRule(0,2,1)); // v0 -> v2 v1 rule[0][0].add(new CNFRule(0,2,3)); // v0 -> v2 v3 rule[1][1].add(new CNFRule(1,'a')); // v1 -> a rule[1][0].add(new CNFRule(1,1,2)); // v1 -> v1 v2 rule[1][0].add(new CNFRule(1,4,5)); // v1 -> v4 v5 rule[1][0].add(new CNFRule(1,2,1)); // v1 -> v2 v1 rule[1][0].add(new CNFRule(1,2,3)); // v1 -> v2 v3 rule[2][1].add(new CNFRule(2,'a')); // v2 -> a rule[2][1].add(new CNFRule(2,'b')); // v2 -> b rule[2][0].add(new CNFRule(2,1,2)); // v2 -> v1 v2 rule[2][0].add(new CNFRule(2,4,5)); // v2 -> v4 v5 rule[2][0].add(new CNFRule(2,2,1)); // v2 -> v2 v1 rule[2][0].add(new CNFRule(2,2,3)); // v2 -> v2 v3 rule[3][0].add(new CNFRule(3,1,2)); // v3 -> v1 v2 rule[4][1].add(new CNFRule(4,'a')); // v4 -> a rule[5][1].add(new CNFRule(5,'b')); // v5 -> b } /** * Initializes this to be a CNF grammar which generates 0*10*, * strings with exactly one '1'. * @post data fields of this CNF grammar are set accordingly */ protected void constructC4() { alpha = new Alphabet("01"); // alphabet symbols numVars = 4; // numVars startToEpsilon = false; // start -> epsilon not a rule rule = new Vector[numVars][2]; // array of Vectors for rules // Create the rule Vectors for(int i=0; i 1 rule[0][0].add(new CNFRule(0,1,2)); // v0 -> v1 v2 rule[0][0].add(new CNFRule(0,1,3)); // v0 -> v1 v3 rule[0][0].add(new CNFRule(0,2,1)); // v0 -> v2 v1 rule[1][0].add(new CNFRule(1,1,1)); // v1 -> v1 v1 rule[1][1].add(new CNFRule(1,'0')); // v1 -> 0 rule[2][1].add(new CNFRule(2,'1')); // v2 -> 1 rule[3][0].add(new CNFRule(3,2,1)); // v3 -> v2 v1 } /** * Initializes this to be a CNF grammar which generates all palindromes * over the alphabet {0,1}. * @post data fields of this CNF grammar are set accordingly */ protected void constructC5() { alpha = new Alphabet("01"); // alphabet symbols numVars = 6; // numVars startToEpsilon = true; // start -> epsilon IS a rule rule = new Vector[numVars][2]; // array of Vectors for rules // Create the rule Vectors for(int i=0; i 0 rule[0][1].add(new CNFRule(0,'1')); // v0 -> 1 rule[0][0].add(new CNFRule(0,1,1)); // v0 -> v1 v1 rule[0][0].add(new CNFRule(0,2,2)); // v0 -> v2 v2 rule[0][0].add(new CNFRule(0,1,3)); // v0 -> v1 v3 rule[0][0].add(new CNFRule(0,2,4)); // v0 -> v2 v4 rule[1][1].add(new CNFRule(1,'0')); // v1 -> 0 rule[2][1].add(new CNFRule(2,'1')); // v2 -> 1 rule[3][0].add(new CNFRule(3,5,1)); // v3 -> v5 v1 rule[4][0].add(new CNFRule(4,5,2)); // v4 -> v5 v2 rule[5][1].add(new CNFRule(5,'0')); // v5 -> 0 rule[5][1].add(new CNFRule(5,'1')); // v5 -> 1 rule[5][0].add(new CNFRule(5,1,1)); // v5 -> v1 v1 rule[5][0].add(new CNFRule(5,2,2)); // v5 -> v2 v2 rule[5][0].add(new CNFRule(5,1,3)); // v5 -> v1 v3 rule[5][0].add(new CNFRule(5,2,4)); // v5 -> v2 v4 } // ** TEST METHOD ** public static void main(String[] args) { new CNF(); } }