Java Anagram耗尽内存

son*_*rin 1 java anagram

我正在努力解决古老的anagram问题.感谢那里的许多教程,我能够遍历一组字符串,递归地找到所有排列,然后将它们与英语单词列表进行比较.我发现的问题是,在大约三个单词(通常是"变态"之类的东西)之后,我得到一个OutOfMemory错误.我尝试将我的批次分成小集,因为它似乎是消耗我所有记忆的递归部分.但即便只是"变态"锁定它......

在这里,我将文件中的单词读入List

Scanner scanner = new Scanner(resource.getInputStream());
   while (scanner.hasNext()) {
       String s = scanner.nextLine();
        uniqueWords.add(s.toLowerCase());
   }
Run Code Online (Sandbox Code Playgroud)

现在我将它们分成更小的集合并调用类来生成字谜:

List<List<String>> subSets = Lists.partition(new ArrayList(uniqueWords), SET_SIZE);

for (List<String> set: subSets) {
      // tried created as class attribute & injection, no difference 
      AnagramGenerator anagramGenerator = new AnagramGenerator();
      List<Word> anagrams = anagramGenerator.createWordList(set);
      wordsRepository.save(anagrams);
      LOGGER.info("Inserted {} records into the database", anagrams.size());
 }
Run Code Online (Sandbox Code Playgroud)

最后我的发电机:

public class AnagramGenerator {

private Map<String, List<String>> map = new Hashtable<>();
public List<Word> createWordList(List<String> dictionary) {

   buildAnagrams(dictionary);

   List<Word> words = new ArrayList<>();
   for (Map.Entry<String, List<String>> entry : map.entrySet()) {
       words.add(new Word(entry.getKey(), entry.getValue()));
   }
    return words;
   }

private Map<String, List<String>> buildAnagrams(List<String> dictionary) {

        for (String str : dictionary) {
            String key = sortString(str);
            if (map.get(key) != null) {
                map.get(key).add(str.toLowerCase());
            } else {
                if (str.length() < 2) {
                    map.put(key, new ArrayList<>());
                } else {
                    Set<String> permutations = permutations(str);
                    Set<String> anagramList = new HashSet<>();

                    for (String temp : permutations) {
                        if (dictionary.contains(temp) && !temp.equalsIgnoreCase(str)) {
                            anagramList.add(temp);
                        }
                    }
                    map.put(key, new ArrayList<>(anagramList));
                }
            }
        }
        return map;
    }

   private Set<String> permutations(String str) {    
        if (str.isEmpty()) {
            return Collections.singleton(str);
        } else {
            Set<String> set = new HashSet<>();
            for (int i = 0; i < str.length(); i++)
                for (String s : permutations(str.substring(0, i) + str.substring(i + 1)))
                    set.add(str.charAt(i) + s);
            return set;
        }
    }
Run Code Online (Sandbox Code Playgroud)

编辑:根据优秀的反馈,我已将我的生成器从排列更改为工作查找:

public class AnagramGenerator {
private Map<String, Set<String>> groupedByAnagram = new HashMap<String, Set<String>>();

    private Set<String> dictionary;

    public AnagramGenerator(Set<String> dictionary) {

        this.dictionary = dictionary;
    }

 public List<Word> searchAlphabetically() {

        List<Word> words = new ArrayList<>();
        for (String word : dictionary) {
            String key = sortString(word);
            if (!groupedByAnagram.containsKey(key)) {
                groupedByAnagram.put(key, new HashSet<>());
            }
            if (!word.equalsIgnoreCase(key)) {
                groupedByAnagram.get(key).add(word);
            }
        }

        for (Map.Entry<String, Set<String>> entry : groupedByAnagram.entrySet()) {
            words.add(new Word(entry.getKey(), new ArrayList(entry.getValue())));
        }

        return words;
    }
 private String sortString(String goodString) {

        char[] letters = goodString.toLowerCase().toCharArray();
        Arrays.sort(letters);
        return new String(letters);
    }
Run Code Online (Sandbox Code Playgroud)

它有更多的调整,所以我不添加一个单词,因为它是自己的字谜,但否则这似乎是快速的.而且,代码更清晰.感谢大家!

sli*_*lim 5

正如长篇词所指出的那样,排列的数量很快就会变得很大.

/usr/share/dict/british-english在Debian上有99,156行.有更长的单词列表,但让我们以此为例.

九个字母单词的排列数为9!= 362,880

因此,对于9个字母或更多的单词,尝试字典中的每个单词的计算工作量要少于尝试输入单词的每个排列.

10! milliseconds = ~1 hour
12! milliseconds = ~5.54 days
15! milliseconds = ~41.44 years
Run Code Online (Sandbox Code Playgroud)

而且你很幸运能够每毫秒处理一个排列,所以你很快就会看到很多与你合作完全不切实际的排列.对堆栈和堆的影响以相同的速率增加.

所以,试试算法(伪代码):

 sorted_input = sort_alphabetically(input_word)
 for each dictionary_word // probably a file readline()
     sorted_dictionary_word = sort_alphabetically(dictionary_word)
     if(sorted_dictionary_word = sorted_input)
         it's an anagram! Handle it
     end 
 end
Run Code Online (Sandbox Code Playgroud)

同样,您可以相当快速地将所有字典单词算法写入查找数据结构.再次伪代码; 在Java中,您可以使用Map<String, List<String>>MultiMap来自Apache Commons或Guava:

  multimap = new MultiMap<String, String> // or whatever

  def build_dict:
      for each dictionary_word // probably a file readline()
          multimap.add(
               sort_alphabetically(dictionary_word), 
               dictionary_word)
      end
  end

  def lookup_anagrams(word):
      return multimap.get(sort_alphabetically(word))
  end 
Run Code Online (Sandbox Code Playgroud)

这会占用适量的内存(整个字典,加上一些键和映射开销),但这意味着一旦创建了结构,就可以非常便宜地反复查询.

如果你想找到双字卦,你需要一个更复杂和有趣的算法.但即便如此,避免暴力破坏整个搜索空间的排列对你的成功至关重要.