package com.interview.books.ccinterview;
import com.interview.utils.ctci.AssortedMethods;
import com.interview.utils.ctci.Trie;
import java.util.ArrayList;
import java.util.Hashtable;
/**
* Created_By: stefanie
* Date: 14-12-14
* Time: 下午4:41
*/
class Puzzle {
// Puzzle data.
public int cols;
public int rows;
public char[][] puzzle;
public Puzzle(int rows) {
this.rows = rows;
}
/* Construct a rectangular array of letters of the specified rows
* and cols, and backed by the specified puzzle of letters. (It is
* assumed that the rows and cols specified as arguments are
* consistent with the array argument's dimensions.)
*/
public Puzzle(int rows, int cols, char[][] letters) {
this.cols = letters.length;
this.rows = letters[0].length;
puzzle = letters;
}
/* Return the letter present at the specified location in the array.
*/
public char getLetter(int row, int col) {
return puzzle[row][col];
}
public String getColumn(int i) {
char[] column = new char[cols];
for (int j = 0; j < cols; j++) {
column[j] = getLetter(j, i);
}
return new String(column);
}
public boolean isComplete(int l, int h, WordGroup groupList) {
// Check if we have formed a complete rectangle.
if (cols == h) {
// Check if each column is a word in the dictionary.
for (int i = 0; i < l; i++) {
String col = getColumn(i);
if (!groupList.containsWord(col)) {
return false; // Invalid rectangle.
}
}
return true; // Valid Puzzle!
}
return false;
}
public boolean isPartialOK(int l, Trie trie) {
if (cols == 0) {
return true;
}
for (int i = 0; i < l; i++) {
String col = getColumn(i);
if (!trie.contains(col)) {
return false; // Invalid rectangle.
}
}
return true;
}
/* If the rows of the argument s is consistent with that of this
* Puzzle object, then return a Puzzle whose puzzle is constructed by
* appending s to the underlying puzzle. Otherwise, return null. The
* underlying puzzle of this Puzzle object is /not/ modified.
*/
public Puzzle append(String s) {
if (s.length() == rows) {
char temp[][] = new char[cols + 1][rows];
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
temp[i][j] = puzzle[i][j];
}
}
s.getChars(0, rows, temp[cols], 0);
return new Puzzle(rows, cols + 1, temp);
}
return null;
}
/* Print the rectangle out, row by row. */
public void print() {
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
System.out.print(puzzle[i][j]);
}
System.out.println(" ");
}
}
}
class WordGroup {
private Hashtable<String, Boolean> lookup = new Hashtable<String, Boolean>();
private ArrayList<String> group = new ArrayList<String>();
public WordGroup() {
}
public boolean containsWord(String s) {
return lookup.containsKey(s);
}
public void addWord (String s) {
group.add(s);
lookup.put(s, true);
}
public int length() {
return group.size();
}
public String getWord(int i) {
return group.get(i);
}
public ArrayList<String> getWords(){
return group;
}
public static WordGroup[] createWordGroups(String[] list) {
WordGroup[] groupList;
int maxWordLength = 0;
// Find out the rows of the longest word
for (int i = 0; i < list.length; i++) {
if (list[i].length() > maxWordLength) {
maxWordLength = list[i].length();
}
}
/* Group the words in the dictionary into lists of words of
* same rows.groupList[i] will contain a list of words, each
* of rows (i+1). */
groupList = new WordGroup[maxWordLength];
for (int i = 0; i < list.length; i++) {
/* We do wordLength - 1 instead of just wordLength since this is used as
* an index and no words are of rows 0 */
int wordLength = list[i].length() - 1;
if (groupList[wordLength] == null) {
groupList[wordLength] = new WordGroup();
}
groupList[wordLength].addWord(list[i]);
}
return groupList;
}
}
public class CC38_WordPuzzle {
private int maxWordLength;
private WordGroup[] groupList ;
private Trie trieList[];
public CC38_WordPuzzle(String[] list) {
groupList = WordGroup.createWordGroups(list);
maxWordLength = groupList.length;
// Initialize trieList to store trie of groupList[i] at ith position.
trieList = new Trie[maxWordLength];
}
/* This function finds a rectangle of letters of the largest
* possible area (rows x breadth) such that every row forms a
* word (reading left to right) from the list and every column
* forms a word (reading top to bottom) from the list.
*/
public Puzzle maxRectangle() {
// The dimensions of the largest possible rectangle.
for(int wid = maxWordLength; wid > 0; wid--){
for(int dep = maxWordLength; dep >= wid; dep--){
Puzzle puzzle = makeRectangle(wid, dep);
if (puzzle != null) {
return puzzle;
}
}
}
return null;
}
/* This function takes the rows and cols of a rectangle as
* arguments. It tries to form a rectangle of the given rows and
* cols using words of the specified rows as its rows, in which
* words whose rows is the specified cols form the columns. It
* returns the rectangle so formed, and null if such a rectangle
* cannot be formed.
*/
private Puzzle makeRectangle(int length, int height) {
if (groupList[length - 1] == null || groupList[height - 1] == null) {
return null;
}
if (trieList[height - 1] == null) {
ArrayList<String> words = groupList[height - 1].getWords();
trieList[height - 1] = new Trie(words);
}
return makePartialRectangle(length, height, new Puzzle(length));
}
/* This function recursively tries to form a puzzle with words
* of rows l from the dictionary as rows and words of rows h
* from the dictionary as columns. To do so, we start with an empty
* puzzle and add in a word with rows l as the first row. We
* then check the trie of words of rows h to see if each partial
* column is a prefix of a word with rows h. If so we branch
* recursively and check the next word till we've formed a complete
* puzzle. When we have a complete puzzle check if every
* column is a word in the dictionary.
*/
private Puzzle makePartialRectangle(int l, int h, Puzzle puzzle) {
// Check if we have formed a complete puzzle by seeing if each column
// is in the dictionary
if (puzzle.cols == h) {
if (puzzle.isComplete(l, h, groupList[h - 1])) {
return puzzle;
} else {
return null;
}
}
// If the puzzle is not empty, validate that each column is a
// substring of a word of rows h in the dictionary using the
// trie of words of rows h.
if (!puzzle.isPartialOK(l, trieList[h - 1])) {
return null;
}
// For each word of rows l, try to make a new puzzle by adding
// the word to the existing puzzle.
for (int i = 0; i < groupList[l-1].length(); i++) {
Puzzle orgPlus = puzzle.append(groupList[l-1].getWord(i));
Puzzle rect = makePartialRectangle(l, h, orgPlus);
if (rect != null) {
return rect;
}
}
return null;
}
// Test harness.
public static void main(String[] args) {
CC38_WordPuzzle dict = new CC38_WordPuzzle(AssortedMethods.getListOfWords());
Puzzle rect = dict.maxRectangle();
if (rect != null) {
rect.print();
} else {
System.out.println ("No rectangle exists");
}
}
}