Have recently been writing a few puzzle solvers in Java, this is my latest attempt. It solves the "Word-Target" puzzle found in some newspapers. You may have seen it before:
Words must be found by using any of the letters in the grid in any order, however each word must contain the centre letter, and must be at least 4 characters long.
The program takes in two text files, one dictionary text file (must be in alphabetical order already) and one puzzle text file:
puzzle.txt:
R O Y S V N E U L
I use the dictionary located here: dict2.txt
Program output:
~~~$ java WordTarget dict2.txt puzzle.txt WORD GRID: r o y s V n e u l Found 38 results with 4 letters or more Found 9 results with 4 letters: - envy - levy - love - oven - over - revs - rove - very - vole Found 12 results with 5 letters: - envoy - lover - loves - nervy - novel - ovens - overs - ovule - roves - servo - solve - voles Found 12 results with 6 letters: - envoys - louver - lovers - novels - overly - ovules - sloven - solver - survey - velour - venous - voyeur Found 4 results with 7 letters: - louvers - nervous - velours - voyeurs Found 0 results with 8 letters: Found 1 results with 9 letters: - nervously Time taken: 524 milliseconds Memory used: 11 MB
WordTarget.java:
/**
* Class Name: WordTarget
*
* @author: Thomas McKeesick
* Creation Date: Monday, February 16 2015, 02:25
* Last Modified: Tuesday, March 03 2015, 11:25
*
* Class Description: A Java class that solves the 9 letter "Word-Target"
* puzzle.
*
* @version 0.2.0
*/
import java.util.List;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Collections;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;
public class WordTarget {
private static int MIN_LENGTH = 4;
private static List<String> dict;
public static void main(String[] args) {
if( args.length < 2 ) {
System.err.println("Usage: java Wordsquare <dictionary> <puzzle>");
System.exit(1);
} else if( args.length == 3 ) {
MIN_LENGTH = Integer.parseInt(args[2]);
}
long startTime = System.currentTimeMillis();
dict = loadDict(args[0]);
char[] grid = loadPuzzle(args[1]);
List<String> results = findStrings(grid);
System.out.println("WORD GRID:");
System.out.println(grid[0] + " " + grid[1] + " " + grid[2] + "\n" +
grid[3] + " " + grid[4] + " " + grid[5] + "\n" +
grid[6] + " " + grid[7] + " " + grid[8] + "\n");
//Removes duplicates from array, then adds back into original array
HashSet<String> set = new HashSet<String>();
set.addAll(results);
results.clear();
results.addAll(set);
Collections.sort(results);
System.out.println("Found " + results.size() + " results with " +
MIN_LENGTH + " letters or more");
for(int i = MIN_LENGTH; i <= grid.length; i++) {
ArrayList<String> tmp = new ArrayList<String>();
for(String s: results) {
if(s.length() == i ) {
tmp.add(s);
}
}
System.out.println("Found " + tmp.size() + " results with " + i +
" letters: ");
for(String s: tmp) {
System.out.println(" - " + s);
}
}
Runtime r = Runtime.getRuntime();
r.gc();
long endTime = System.currentTimeMillis();
System.out.println("Time taken: " + (endTime - startTime) +
" milliseconds");
System.out.println("Memory used: " +
((r.totalMemory() - r.freeMemory())/1024/1024) + " MB");
System.exit(0);
}
/**
* Private method to load a puzzle text file into a char array
* @param filename The name of the file to load
* @return The char array containing the text file, with the "centre" letter
* in upper case to avoid finding duplicate letters when searching
* in the permute method.
*/
private static char[] loadPuzzle(String filename) {
char[] grid = new char[9];
try {
BufferedReader in = new BufferedReader(new FileReader(filename));
String line;
int n = 0;
while( (line = in.readLine()) != null ) {
String[] row = line.split("\\s");
for(int i = 0; i < 3; i++) {
if( n != MIN_LENGTH ) {
grid[n] = Character.toLowerCase(
row[i].charAt(0));
} else {
grid[n] = Character.toUpperCase(
row[i].charAt(0));
}
n++;
}
}
} catch( IOException e ) {
System.err.println("A file error occurred: " + filename +
"Error message: " + e.getMessage() +
e.getStackTrace());
System.exit(1);
}
return grid;
}
/**
* A method that loads a dictionary text file into a tree structure
* @param filename The dictionary file to load
* @return The ArrayList containing the dictionary
*/
private static List<String> loadDict(String filename) {
dict = new ArrayList<String>();
try {
BufferedReader in = new BufferedReader(
new FileReader(filename));
String word;
while( (word = in.readLine()) != null ) {
dict.add(word);
}
} catch( IOException e ) {
System.err.println("A file error occurred: " + filename );
System.exit(1);
}
return dict;
}
/**
* Private method to call the permute function, provides a List to
* populate and the "centre" character.
* @param grid The puzzle grid to solve
* @return The List containing the words found in the puzzle
*/
private static List<String> findStrings(char[] grid){
List<String> tmp = new ArrayList<String>();
String str = new String(grid);
char centre = grid[4];
tmp = permute(tmp, str, MIN_LENGTH,
String.valueOf(centre));
return tmp;
}
/**
* Outer function to call the recursive permute function
* @param words The ArrayList to populate
* @param str The string containing the letters to permute, the "centre"
* letter is capitalised.
* @param minLength The minimum amount of letters that a word must contain
* @param centre The "centre" letter that each word must contain
* @return The ArrayList of all dictionary words found in str
*/
private static List<String> permute(List<String> words, String str,
int minLength, CharSequence centre) {
permute("", str, words, minLength, centre);
return words;
}
/**
* The recursive permute function. Generates every permutation of every
* length from 0-the length of the string. Checks whether each string is
* larger than the minimum length supplied, contains the "centre" letter,
* and is contained in the dictionary. If so, adds it to the ArrayList
* of strings to be returned.
*/
private static void permute(String prefix, String str, List<String> words,
int minLength, CharSequence centre) {
int length = str.length();
if(prefix.length() >= minLength && prefix.contains(centre) &&
Collections.binarySearch(dict, prefix.toLowerCase()) >= 0) {
words.add(prefix.toLowerCase());
}
if( length != 0 ) {
for(int i = 0; i < length; i++) {
permute(prefix + str.charAt(i),
str.substring(0, i) + str.substring(i+1, length),
words, minLength, centre);
}
}
}
}
I was just looking for any general advice on style or efficiency. I like the elegance of the recursive solution currently but it still checks 986 410 strings. Was thinking of creating a "reverse dictionary" of strings that are 3/4 characters long that are impossible starting sequences for words, such as "abx". This could save quite a lot of recursion.