Java:查找相似的字符串

Java: Find similar strings

我有一个包含很多字符串的 java 列表(如果需要,它可以是地图)。

我想以高效的方式为另一个给定字符串找到最相似的字符串。

我想我应该使用 Levenshtein 距离,但我不想遍历所有列表。

您认为将主列表分成一些具有共同前缀的部分是个好主意吗?

然后我会有一个以前缀为键、以列表为值的映射:

这样我可以快速搜索与搜索到的字符串具有相同前缀的字符串。然后我可以只对一些字符串而不是所有主列表应用 Levenshtein 距离。

这是个好主意吗?谢谢

您可以一次计算每个词条的soundex代码,并将soundex映射到原始单词列表。 Soundex 是一个简化代码,可以为相似发音的单词获取一个键。

Map<String, Set<String>> soundexToWords = ...
for (String word : words) {
    String sdex = soundex(word);
    Set<String> similarWords = soundexToWords.get(sdex));
    if (similarWords == null) {
        similarWords = new HashSet<>();
        soundexToWords.put(sdex, similarWords);
    }
    similarWords.add(word);
}

Set<String> similarWords(String word) {
    return soundexToWords.get(soundex(word));
}

Soundex 通常用于一种语言,比如英语,尤其是对于英语,它是相当简化的。

简单的解决方案

最简单的解决方案是先在您的 List 上调用 Collections.sort()。现在您的列表已按 lexicographical order 排序。接下来,对列表执行二进制搜索以查找您的前缀所属的位置。返回的索引基本上为您提供了最相似词的位置。

然后您可以通过将前缀映射到索引来构造您的地图,这样您就可以根据需要检索整体 List 的子集,或者您可以将子集本身缓存在地图中。该子集是一个列表,从整体 List 中的该索引开始,其元素具有递减的相似性。您可以将停止索引微调为一旦第一个字母递增或类似的东西。

最佳解决方案

最好的解决办法是看看trie数据结构。 trie 支持 O(m) 查询,其中 m 是您要搜索的前缀的长度。这占用 少得多 space 并且避免哈希冲突。虽然您的地图理论上支持 O(1) 查询,但如果您明确存储每个列表,构建时间为 O(n^2)。如果存储索引,构建时间是线性的,但是每个请求都是 O(n).

public List<String> similarWords(String word, List<String> allWords){
    List<String> similarWordList = new ArrayList<>();

    for(String currentWord : allWords){
        if(currentWord.contains(word)){
            similarWordList.add(currentWord);
        }
    }
    return similarWordList;
}