首页 > 解决方案 > 使用部分单词匹配而不是余弦相似度在java中搜索2个字符串的相似度

问题描述

嗨,我想通过其他字符串中出现的部分单词来比较 2 个字符串。例如:我有 4 个字符串:

A) "white snow ball"
B) "super exciting"
C) "white image superdupercold"
D) "cold"

并且用户通过此字符串“ super cold white snow”搜索结果应按以下顺序返回:

C, A, D, B

因为 B 只有一个匹配“super”,总共 2 个单词(相似度 = 50%),而 D 匹配一个“cold”,总共 1 个单词(相似度 = 100%)。A 有 2 个匹配“white”和“snow”,C 有 3 个匹配但是,如果我使用余弦相似度,它的排名会有所不同: 余弦相似度的结果

另一个例子:如果用户通过这个字符串“super”搜索,那么结果应该按这个顺序返回:B,C

我认为它可以通过正则表达式和字符串拆分来解决。有没有什么好的和干净的方式来用 java 写它?

标签: javaregexsplit

解决方案


haystack.split("\\s+")对于每个搜索字符串,使用( \\s+is regexp-ese for '字符串由空格分隔')将其拆分为单词。

然后,要获得“分数”,您需要 2 个数字:匹配的单词数,以及总共有多少单词。您首先按降序排序,最后按升序排序,这会得到您想要的行为。

String[] needle = "super cold white snow".split("\\s+");
String[] haystack = "white image superdupercold".split("\\s+");
int matchedWords = 0, totalWords = haystack.length;
for (String n : needle) {
    boolean found = false;
    for (String hay : haystack) {
        if (hay.contains(n)) {
            found = true;
            break;
        }
    }
    if (found) matchedWords++;
}

对于每根针,您现在最终得到 2 个分数:matchedWords 和 totalWords。

对于给定的任何 2 个这样的得分对,获胜者是匹配词数较高的那个;totalWords 用作决胜局,它反向工作(较低的 totalWords 获胜)。

有多种方法可以尝试表示这一点。一个简单的技巧是将所有这些“编码”为一个长值:

private static final long MULTIPLIER = 0x100000000L;
long score = MULTIPLIER * matchedWords + (Integer.MAX_VALUE - totalWords);

现在得分较高的针是最好的答案。

另一种选择是创建一个代表针的类以及两个分数,将所有结果放在一个列表中,然后对列表进行排序:

@Value
class Result { String needle; int words, total; }

list.sort(
    Comparator.comparing(Result::getWords).reversed().
    thenComparing(Comparator.comparing(Result::getTotal));

list.stream().map(Result::getNeedle).forEach(System.out::println);

注意:如果目标是非常有效地做到这一点,那么您可以快速处理几十万个大海捞针,答案在于诸如 postgres tsvectors 之类的 wordsearch 解决方案或Lucene之类的库。

这些片段中使用的类型:

  • 龙目岛的@Value
  • java.util.Comparator

推荐阅读