/* (c) Nathaniel Lim Williams College CS256, HW5, #3c 3/20/09 A Dynamic Programming Solution to the "Pretty Poetry" Problem using recursive memoization with HashMaps. Word List: words Pair: represents a continuous subsection of the world list: words[i, j] Compile: javac PP.java Run: java PP {Line Length} {input.txt} */ import java.lang.Integer; import java.util.Scanner; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.Collections; import java.util.Set; import java.util.HashSet; public class PP{ public static HashMap<Pair, Integer> minSlackTable = new HashMap<Pair, Integer>(); //Subsection -> minSlack^2 Value public static HashMap<Pair, Integer> kValues = new HashMap<Pair, Integer>(); // Subsection -> Optimal spot to split on public static ArrayList<String> words = new ArrayList<String>(); //A List of all the words in the input file public static int L; //The Desired Number of Lines Line Length public static void main (String [] args) throws java.io.FileNotFoundException { if (args.length !=2){ System.out.println("Please Enter: {Line Length} {filename.txt}"); } else { L = Integer.parseInt(args[0]); File f = new File(args[1]); Scanner sc = new Scanner (f); sc.useDelimiter("\\s"); while (sc.hasNext()){ words.add(sc.next()); } int size = words.size(); Integer answer = minSlack2(0, size-1); //Now the minSlackTable and the KValues are are filled in ArrayList<Integer> partitions = getPartitions(0, size-1); //Partitions is the list of ks that split lines Collections.sort(partitions); //Make sure that p is in order for printing // Print out the lines, go to next line once you hit a partitions(s) int k; int i = 0; for (int s = 0; s < partitions.size(); s++){ k = partitions.get(s); while ( i <= k){ if (i < k){ System.out.print(words.get(i) + " "); } else { System.out.println(words.get(i)); } i++; } } } } //Recursively finds the k values that are used in the optimal solution public static ArrayList<Integer> getPartitions(int i, int j){ ArrayList<Integer> p = new ArrayList<Integer>(); Pair section = new Pair(i, j); if (kValues.containsKey(section)){ //Look up the best place to split int bestK = kValues.get(section).intValue(); if (bestK >= j){ //If it past the section, just split at the end p.add(j); } else { //Otherwise concat list of best splits of left to best splits of right p.addAll(getPartitions(i, bestK)); p.addAll(getPartitions(bestK+1, j)); } return p; } else { return null; } } //This is the dynamic programming algorithm that finds the minSlack^2 for an interval //Keeps minSlackTable values memoized, and keep track of the optimal k choices //in a table called kValues public static Integer minSlack2(int i, int j){ Pair section = new Pair (i, j); if (minSlackTable.containsKey(section)){ //Already has been memoized return minSlackTable.get(section); } else { if (j < i){ //null interval minSlackTable.put(section, 0); return 0; } else if (i == j) { //Just one word int r1 = slack2(i, j); minSlackTable.put(section, r1); kValues.put(section, j+1); return r1; } else if (lineLength(i, j) <= L){ //If the words stay within a line, it is // the optimal solution. int r2 = slack2(i, j); minSlackTable.put(section, r2); kValues.put(section, j+1); return r2; } else { //Recursive Case: the lineLength(i,j) > L //So find the best subdivision of the section //Find the minimum sum of squares of slacks //by iterating through all the possiblities and //choosing the minimum one. int minValue = Integer.MAX_VALUE; int k; int bestK = -1; for (k = i; k < j; k++){ int test_slack = minSlack2(i, k) + minSlack2(k+1, j); if (test_slack < minValue){ minValue = test_slack; bestK = k; } } // Update Tables, For this sections we have found // The best k value to split up the line // And the minSlack^2 value for that k minSlackTable.put (section, minValue); kValues.put (section, bestK); return minValue; } } } // Computes the length of a line from word i to word j inclusive public static int lineLength(int i, int j){ if (i==j){ return words.get(i).length(); } int output = 0; for (int a = i; a <= j; a++){ output += words.get(a).length() + 1; if ( a == j){ output--; } } return output; } // Computes the Squre of the Slack of the Line public static int slack2(int i, int j){ int temp = L - lineLength(i, j); return temp*temp; } } // A Helper class that is used as the key for the minSlackTable and kValues HashMaps // It represents a continuous subsection of the words class Pair{ int i, j; public Pair (){ } public Pair(int i, int j){ this.i = i; this.j = j; } @SuppressWarnings("unchecked") public boolean equals(Object o){ if (o instanceof Pair){ Pair p = (Pair)o; return this.i == p.i && this.j == p.j; } else { return false; } } public int hashCode(){ Integer oi = new Integer(i); Integer oj = new Integer(j); return oi.hashCode()+ 4*oj.hashCode(); } public String toString(){ return "(" + i + ", " + j + ")"; } }