我正在编写一个填字游戏的解算器,它可以读入一个字典文件并给出一个模式,返回一个符合该模式的所有单词的列表。我有功能,但我需要这个工作更快。我创建了一个HashMap,其中单词的长度是键,单词的ArrayList作为值。有没有我可以更快地读取ArrayList或者是否有更好的数据结构可供使用?
import java.util.*;
public class CWSolution {
//create the data structure that will store the dictionary
private HashMap<Integer,ArrayList<String>> db;
public CWSolution(List<String> allWords)
{
//construct the background structure
//Create hashmap
db = new HashMap<Integer,ArrayList<String>>();
//go through each word
for(String item : allWords ){
//if the db does not contain a listing for this word length, create one
if(!db.containsKey(item.length())){
ArrayList<String> temp = new ArrayList<String>();
temp.add(item);
db.put(item.length(), temp);
}
//if it does contain a listing for this word length, add this word to it
else{
ArrayList<String> temp = db.get(item.length());
temp.add(item);
db.put(item.length(), temp);
}
}
}
public List<String> solutions(String pattern, int maxRequired)
{
//actually look for each pattern
//create the structures we need
List<String> answer = new ArrayList<String>();
//get the relevant array list
ArrayList<String> temp = db.get(pattern.length());
//go through the array list word by word
for(String item : temp ){
//see if the pattern matches the word, if it does add it to the list, otherwise skip it
if(matchPattern(pattern, item)){
answer.add(item);
}
//if we reach the required size return it, otherwise keep going
if(answer.size() == maxRequired){
return answer;
}
}
return answer;
}
private boolean matchPattern(String pattern, String word){
//TODO implement this function
//check the word against the pattern
char star = "*".charAt(0);
for(int i=0;i<pattern.length();i++){
if(pattern.charAt(i) != star){
if(pattern.charAt(i) != word.charAt(i)){
return false;
}
}
}
return true;
}
}
修改 添加更多信息以使其更加清晰。
有些评论正在讨论这个问题,所以我明白了,我是数据结构课程的学生,所以我知道的很多,但是我们即将结束学期,所以我有一个基本数据结构的好主意。
此外,我并不关心优化CWSolution()方法,而是优化solution()方法。速度正在测试如下,我真正关心的是Time2。这是找到匹配单词所需的时间,而不是创建结构需要多长时间。
import java.util.Date;
import java.util.List;
public class CWSpeedTest {
public static void main(String[] args){
try{
FileParser fp = new FileParser("TWL06.txt");
List<String> solutions = null;
//Change this to change the pattern
String pattern = "*S**";
//Change this to change the max solutions
int maxSolns = 2000;
List<String> dict = fp.getAllWords();
Date d1 = new Date();
CWSolution c = new CWSolution(dict);
Date d2 = new Date();
for (int i = 0; i < 1000; i++)
solutions = c.solutions(pattern,maxSolns);
Date d3 = new Date();
System.out.println("Time 1: " + (d2.getTime() - d1.getTime()));
System.out.println("Time 2: " + (d3.getTime() - d2.getTime()));
System.out.println("For the pattern: " + pattern);
System.out.println("With max solutions: " + maxSolns);
System.out.println(solutions);
}catch (Exception e){
e.printStackTrace();
}
}
}
答案 0 :(得分:3)
这是使用索引对所有位置和字符完全重写算法。首先,该算法在模式中找到的指定位置找到具有指定字符的单词的最短列表。然后它计算所有其他单词列表的横截面(模式中每个非星形字符一个列表)。
import java.util.*;
public class CWSolution {
class FixLengthDB {
// Index -> Letter -> All word with the Letter at Index
HashMap<Integer, HashMap<Character, Set<String>>> indexLetterDb = new HashMap<>();
public void storeWord(String word) {
int l = word.length();
for (int i = 0; i < l; i++) {
HashMap<Character, Set<String>> letterDb = indexLetterDb.get(i);
if (letterDb == null) {
letterDb = new HashMap<>();
indexLetterDb.put(i, letterDb);
}
Set<String> list = letterDb.get(word.charAt(i));
if (list == null) {
list = new HashSet<>();
letterDb.put(word.charAt(i), list);
}
list.add(word);
}
}
public Set<String> getList(int i, char c) {
HashMap<Character, Set<String>> letterDb = indexLetterDb.get(i);
if (letterDb == null) {
return null;
}
return letterDb.get(c);
}
}
//create the data structure that will store the dictionary
private HashMap<Integer,FixLengthDB> db = new HashMap<>();
private List<String> allWords;
public CWSolution(List<String> allWords)
{
//construct the background structure
this.allWords = allWords;
//go through each word
for(String item : allWords) {
FixLengthDB fixLengthDB = db.get(item.length());
if (fixLengthDB == null) {
fixLengthDB = new FixLengthDB();
db.put(item.length(), fixLengthDB);
}
fixLengthDB.storeWord(item);
}
}
public List<String> solutions(String pattern, int maxRequired)
{
FixLengthDB fixLengthDB = db.get(pattern.length());
if (fixLengthDB == null) {
return new ArrayList<>();
}
Set<String> shortList = null;
int shortListIndex = 0;
int l = pattern.length();
for (int i = 0; i < l; i++) {
if (pattern.charAt(i) == '*') {
continue;
}
Set<String> set = fixLengthDB.getList(i, pattern.charAt(i));
if (set == null) {
return new ArrayList<>();
}
if (shortList == null || shortList.size() > set.size()) {
shortList = set;
shortListIndex = i;
}
}
if (shortList == null) {
return allWords;
}
HashSet<String> result = new HashSet<>(shortList);
for (int i = 0; i < l; i++) {
if (i == shortListIndex || pattern.charAt(i) == '*') {
continue;
}
Set<String> set = fixLengthDB.getList(i, pattern.charAt(i));
result.retainAll(set);
}
// TODO truncate result list according to 'maxRequired' parameter
return new ArrayList<>(result);
}
}
解释:该算法分两步进行
solutions(...)
)构建索引:索引维护每个字长/字符索引/字符的字符串集。
这里我们如何向索引添加单词
Add word: fun
|||
||\--- (length: 3, position 3, character 'n') -> set{"fun"})
|\---- (length: 3, position 2, character 'u') -> set{"fun"})
\----- (length: 3, position 1, character 'f') -> set{"fun"})
Add word: run
|||
||\--- (length: 3, position 3, character 'n') -> set{"fun, run"})
|\---- (length: 3, position 2, character 'u') -> set{"fun, run"})
\----- (length: 3, position 1, character 'r') -> set{"run"})
Add word: raw
|||
||\--- (length: 3, position 3, character 'w') -> set{"raw"})
|\---- (length: 3, position 2, character 'a') -> set{"raw"})
\----- (length: 3, position 1, character 'r') -> set{"run, raw"})
Add word: rar
|||
||\--- (length: 3, position 3, character 'r') -> set{"rar"})
|\---- (length: 3, position 2, character 'a') -> set{"raw, rar"})
\----- (length: 3, position 1, character 'r') -> set{"run, raw, rar"})
添加四个单词(fun,run,raw,rar)后的数据库是
(length: 3, position 1, character 'f') -> set{"fun"})
(length: 3, position 1, character 'r') -> set{"run, raw, rar"})
(length: 3, position 2, character 'u') -> set{"fun, run"})
(length: 3, position 2, character 'a') -> set{"raw, rar"})
(length: 3, position 3, character 'w') -> set{"raw"})
(length: 3, position 3, character 'r') -> set{"rar"})
(length: 3, position 3, character 'n') -> set{"fun, run"})
使用索引:如何匹配模式ru*
首先让我们在索引中找到匹配的最小集合。我们只有2个非星形字符,所以我们只检查两组
1: (length: 3, position 1, character 'r') -> set{"run, raw, rar"})
2: (length: 3, position 2, character 'u') -> set{"fun, run"})
最小的一组是#2 {"fun, run"}
。现在我们遍历所有其他匹配集(在我们的例子中是集合#1)并计算交叉点:
{"fun, run"} cross {"run, raw, rar"} => {"run"}
结果为{"run"}
。
答案 1 :(得分:2)
如果您想优化查找速度,那么您理想的解决方案就是您所知道的所有(模式的所有内容),它只为您提供 匹配的单词集。实际上,您将使用您所知道的某些来缩小到可接受大小的集合。
在原始代码中,您只使用一个您知道的项目(即长度)作为密钥。 Millimoose的评论提供了正确答案:创建一个更具辨别力的密钥。例如,假设你有一个双字段键:(长度,字符包含)...即1A,1B,1C,... 1Z,2A ...如果每个指向一个集合,每个集合将更小。然后,您可以使用长度和模式中的任何字母来获得该组。
更进一步,你可以让Millimoose成为一个三字段键(长度,位置,字符)。这样,您可以从模式中获取任何字母,并使用这三个属性将其缩小到更小的列表。 [正如Millimoose指出的那样,减慢你的速度就是字符串比较。]
理论上,你可以一路走下去,为每种可能的模式设置一套。例如,单词“man”将适合模式"m**","ma*","m*n","*a*","ma*","*an","**n","m*n" and "*an"
。这些中的每一个都可以是地图中的关键字,其指向包含单词“man”的列表(值)。例如,“m **”将指向包含“man”,“mob”,“map”,“mid”等的列表。
当你这样做时,在构建数据结构时,最初可能会花费太多时间。此外,您可能最终没有足够的空间来保存该数据结构。这些是权衡利弊。
总之,从当前密钥开始,请考虑在密钥中添加更多信息,同时权衡这样做的成本。