Scrabble Anagram Generator
I am trying to write a scrabble anagram generator.
So far my code sort of works, but it's horribly slow, and has bugs. One being it will use letters more than once. For example: Letters inputted: "ABCDEFG". And it will generate AB, but also AA, which isn't right.
Please help.
public class Scrabble1
{
private String[] dictionary2 = new String[97];
private String[] dictionary3 = new String[978];
private String[] dictionary4 = new String[3904];
private String[] dictionary5 = new String[8635];
private String[] dictionary6 = new String[15225];
private String[] dictionary7 = new String[23097];
public void sampleMethod(String s) throws FileNotFoundException
{
File in2 = new File( "dictionary2.txt" );
File in3 = new File( "dictionary3.txt" );
File in4 = new File( "dictionary4.txt" );
File in5 = new File( "dictionary5.txt" );
File in6 = new File( "dictionary6.txt" );
File in7 = new File( "dictionary7.txt" );
Scanner dict2 = null,dict3 = null,dict4 = null,dict5 = null,dict6 = null,dict7 = null;
try
{
dict2 = new Scanner(in2);
dict3 = new Scanner(in3);
dict4 = new Scanner(in4);
dict5 = new Scanner(in5);
dict6 = new Scanner(in6);
dict7 = new Scanner(in7);
int c = 0;
while(dict2.hasNext()&&dict3.hasNext()&&dict4.hasNext()&&dict5.hasNext()&&dict6.hasNext()&&dict7.hasNext())
{
dictionary2[c] = dict2.next();
dictionary3[c] = dict3.next();
dictio开发者_JAVA百科nary4[c] = dict4.next();
dictionary5[c] = dict5.next();
dictionary6[c] = dict6.next();
dictionary7[c] = dict7.next();
c++;
}
}
catch( FileNotFoundException e )
{
System.err.println( e.getMessage () );
System.exit(1);
}
finally
{
dict2.close();
dict3.close();
dict4.close();
dict5.close();
dict6.close();
dict7.close();
}
// for(int i= 0; i<80612; i++)
//System.out.println(dicArray[i]);
String temp = "";
//All 2 letter anagrams
for(int k=0; k<=6; k++)
for(int i=0; i<=6; i++)
for(int d= 0; d<97; d++)
{
temp = "" + s.charAt(k) + s.charAt(i);
if(temp.equals(dictionary2[d]))
System.out.println(temp );
}
//All 3 letter anagrams
for(int j = 0; j<=6; j++)
for(int k=0; k<=6; k++)
for(int i=0; i<=6; i++)
for(int d= 0; d<978; d++)
{
temp = "" + s.charAt(j) + s.charAt(k)+ s.charAt(i);
if(temp.equals(dictionary3[d]))
System.out.println(temp );
}
//All 4 letter anagrams
for(int j = 0; j<=6; j++)
for(int k = 0; k<=6; k++)
for(int i=0; i<=6; i++)
for(int l=0; l<=6; l++)
for(int d= 0; d<-3904; d++)
{
temp = "" + s.charAt(j) + s.charAt(k)+ s.charAt(i)+ s.charAt(l);
if(temp.equals(dictionary4[d]))
System.out.println(temp );
}
//All 5 letter anagrams
for(int j = 0; j<=6; j++)
for(int k = 0; k<=6; k++)
for(int i=0; i<=6; i++)
for(int l=0; l<=6; l++)
for(int f=0; f<=6; f++)
for(int d= 0; d<8635; d++)
{
temp = "" + s.charAt(j) + s.charAt(k)+ s.charAt(i)+ s.charAt(l)+s.charAt(f);
if(temp.equals(dictionary5[d]))
System.out.println(temp );
}
//All 6 letter anagrams
for(int j = 0; j<=6; j++)
for(int k = 0; k<=6; k++)
for(int i=0; i<=6; i++)
for(int l=0; l<=6; l++)
for(int f=0; f<=6; f++)
for(int g=0; g<=6; g++)
for(int d= 0; d<15225; d++)
{
temp = "" + s.charAt(j) + s.charAt(k)+ s.charAt(i)+ s.charAt(l)+ s.charAt(f)+ s.charAt(g);
if(temp.equals(dictionary6[d]))
System.out.println(temp );
}
//All 7 letter anagrams.
for(int j = 0; j<=6; j++)
for(int k = 0; k<=6; k++)
for(int i=0; i<=6; i++)
for(int l=0; l<=6; l++)
for(int f=0; f<=6; f++)
for(int g=0; g<=6; g++)
for(int p=0; p<=6; p++)
for(int d= 0; d<23097; d++)
{
temp = "" + s.charAt(j) + s.charAt(k)+ s.charAt(i)+ s.charAt(l)+ s.charAt(f)+ s.charAt(g)+ s.charAt(p);
if(temp.equals(dictionary7[d]))
System.out.println(temp );
}
}
}
Dictionary files are just sorted by word size.
You could build a trie out of the dictionary and traverse it. For each character in the input string, go to the corresponding node in the trie, remove the character from the input and repeat recursively.
Pseudo-code:
function check(trie_node)
if trie_node is terminal
output trie_node
else
for each child of trie_node
let c be the character of the child
if input contains at least one c
remove one c from input
check(child)
put c back into input
end
end
end
end
check(trie_root)
You could use a lookup table to quickly check how many of a certain character there are left in the input (constant time check).
I would approach this by first unifying all of your dictionaries into one giant dictionary, and then sorting the letters in the dictionary you build and the word you're searching for subsets of called searchWord.
I would do something like this
String findAllScrabbleWords (String searchWord)
searchWord = searchWord.sortLetters();
Dictionary<String,List<String>> wordlist = new Dictionary <String, List<String>>()
foreach file in fileList
foreach word in file
sortedword = word.sortLetters();
// Add a new key if it isn't there then add the new word
if (!wordlist.containsKey(sortedword))
wordlist[sortedword] = new List<String>();
wordlist[sortedword].add(word);
end
// Now search for the words.
return findScrabbleWords ("", sortedword, wordList);
end
// We do this recursively so we don't have to worry about how long the search
// string is.
String function findScrabbleWords (String headString, String tailString, Dictionary<String,List<String>> wordList)
if (tailString == "")
return "";
end
String returnValue = "";
for (pos = 0; pos < tailString.length; pos++)
// Add an element of the tail to the current string and remove
// that letter from the tail.
String currString = headString + tailString[pos];
String remainderString = tailString.removeAt(pos,1);
if (wordList.containsKey(currString))
foreach word in wordList[currString]
returnValue += word + " ";
end
end
// Now check the strings that contain the new currString
returnValue += findScrabbleWords(currString,remainderString,wordList);
end
return returnValue;
end
Your question boils down to the following basic algorithms:
- Generate all possible subsets of a given set
- Most easily done with a bitfield counter
- Generate all permutations of a set
- Pseudocode description and diagram
- A more specific implementation for .NET strings
I should also note that one problem with your current code is that all your inner loops start from 0, which is not correct. This is why "AA" is generated (because you end up returning the character for index 0 twice).
A bitfield counter in Java
package com.stackoverflow.samples;
import java.lang.String;
public class Main {
public static void main(String[] args) {
String input = "ABCDE";
printAllSubsets(input);
}
private static void printAllSubsets(String input) {
int n = input.length();
int last = 2 << n;
char[] subset = new char[n];
for (int bits = 0; bits < last; ++bits) {
int j = 0;
for (int i = 0; i < n; ++i) {
if (bitIsSet(bits, i)) {
subset[j] = input.charAt(i);
++j;
}
}
printSubset(subset, j);
}
}
private static void printSubset(char[] subset, int n) {
System.out.print('{');
for (int i = 0; i < n; ++i) {
System.out.print(subset[i]);
}
System.out.println('}');
}
private static boolean bitIsSet(int bits, int position) {
return ((bits >> position) & 1) == 1;
}
}
In Python:
import itertools
mystring = "ABCDEFG"
for perm in itertools.permutations(mystring):
print "".join(perm)
And if you want to see the algorithm, just look at the source/docs:
def permutations(iterable, r=None):
# permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC
# permutations(range(3)) --> 012 021 102 120 201 210
pool = tuple(iterable)
n = len(pool)
r = n if r is None else r
if r > n:
return
indices = range(n)
cycles = range(n, n-r, -1)
yield tuple(pool[i] for i in indices[:r])
while n:
for i in reversed(range(r)):
cycles[i] -= 1
if cycles[i] == 0:
indices[i:] = indices[i+1:] + indices[i:i+1]
cycles[i] = n - i
else:
j = cycles[i]
indices[i], indices[-j] = indices[-j], indices[i]
yield tuple(pool[i] for i in indices[:r])
break
else:
return
Jon Bentley's book, Programming Pearls, has a great example of doing this for anagrams and I'm sure you could adapt it. See the code for column 2 (or even better grab the book!).
I'll sketch out an implementation here:
1) Go through a dictionary, for each word sort the letters into order (e.g. fish would become "fihs", "donkey" would become "dekony". This key will allow you to look up all the words that can be made with this series of letters. Store this information in a data structure Map<String,Set<String>>. For example, for the word dog you'd end up with two entries dog -> (god,dog).
3) Now when you want to find a word, sort the sequence of letters in the rack as described above and query the map (e.g. look up the key in the Map you've made). This'll give you the list of all possible words made from that series of letters.
You'll have adapt this a little for Scrabble because the original algorithm was for anagrams, but it should be as simple as just querying the map more times (e.g. if you have the letters dayvgea then you'd need to query not only for aadgeyv, but also for each combination of 6 letters and below. The number of distinct combinations of 7 items is only 128, so to find the best word you'll only need a fixed number of lookups in the data structure.
I appreciate all the help you have all provided. I took a simpler approach, here it is: It seems to be quite efficient, but I still plan to investigate all the alternatives you posed.
public class Unscramble
{
public final static String letters = JOptionPane.showInputDialog("Please input your tiles").toLowerCase();
public static LinkedList<String> words = new LinkedList();
public static void main(String[] args) throws FileNotFoundException, IOException
{
checkWords(new FileReader("ospd3.txt"));
for(int i = 0; i < words.size(); i++)
{
System.out.println(words.get(i));
}
}
private static void checkWords(FileReader dict) throws IOException
{
BufferedReader bf = new BufferedReader(dict);
String line = "";
while((line = bf.readLine()) != null)
{
if(hasWord(line))
{
words.add(line);
}
}
bf.close();
dict.close();
}
public static boolean hasWord(String word)
{
String copy = letters;
for(int u = 0; u < word.length(); u++)
{
if(copy.contains(String.valueOf(word.charAt(u))))
{
copy = copy.replaceFirst(String.valueOf(word.charAt(u)), "");
}
else
{
return false;
}
}
return true;
}
}
精彩评论