找到最相似价值的有效方法

时间:2016-01-21 07:38:12

标签: java string similarity

我有一个像Color这样的值,还有一个String列表:{Color,Color,Main Color,Main Color,Theme,Brand,Subject ..... etc}

我想获得最相似的字符串,但搜索的字符串本身除外。在这个例子中,期望获得Color。 (不是颜色)

我正在整理清单 我使用以下规则并对规则进行排名:

  1. 过滤相同的值
  2. 检查大写小写
  3. 删除空格。修剪
  4. 使用Levenshtein距离
  5. 字符串顺序:主要颜色=主要颜色
  6. 检查首字母缩略词:HP - Hewlett Packard
  7. 需要花费大量时间来查看1000名相关候选人的名单。此外,我有很多候选人要检查。

    还有其他任何有效的方式吗?

    原始代码:

    allowPointSelect: true, 
    

    谢谢, 微米。

2 个答案:

答案 0 :(得分:1)

你的问题是时间复杂性之一。 Collections.sort()是一个O(n log n)操作,这是compare方法的调用次数。问题在于Levenshtein是一个“昂贵”的计算。

您可以通过找到为每个项目精确计算一次的方法来提高排序性能,使Levenshtein计算为O(n),运算,然后对存储的计算距离进行排序。

我使用各种列表大小排序随机整数列表进行了测试,实际调用compare()的次数非常接近n log 2 n,所以对于一个大约1000个字符串的列表,它将快10倍左右,因为log 2 (1000)大约是10个。

您可以通过不排序进一步提高效果,但只需获取指定同一比较器的最小项。

另一个改进是避免distinct()调用(相对昂贵),使用Set(强制执行唯一性)来保留候选者。

如果可以,请填充已经训练和小写的值的候选者,这样就可以避免每次运行时修剪和小写以及小写。输入相同内容,这样您就可以使用equals()代替较慢的equalsIgnoreCase()

这是一种方式:

import static org.apache.commons.lang.StringUtils.getLevenshteinDistance;

String search; // your input
Set<String> candidates = new HashSet<>(); // populate this with lots of values
Map<String, Integer> cache = new ConcurrentHashMap<>();
String closest = candidates.parallelStream()
    .map(String::trim)
    .filter(s -> !s.equalsIgnoreCase(search))
    .min((a, b) -> Integer.compare(
      cache.computeIfAbsent(a, k -> getLevenshteinDistance(search, k)),
      cache.computeIfAbsent(b, k -> getLevenshteinDistance(search, k))))
    .get();

此代码在1000个随机候选者中执行约50ms,在100万个候选者中执行约1秒。

答案 1 :(得分:0)

<强>被修改

我将波希米亚语给出的答案包含在原始代码的上下文中,以便您更好地理解。

.map(term -> Arrays.stream(term.split(" ")).sorted().collect(Collectors.joining(" ")))再次拆分多字词,排序和连接以消除相同词的排列。这是对诸如&#34;主要颜色&#34;等主题的排列平等挑战的回答。和&#34;颜色主要&#34;。

但是,在此问题的上下文中捕获任务的所有业务需求是没有意义的。通过这个答案,您可以得到解决方案的概要。解决了效率问题。你可能需要更多的阶段,但这是一个不同的故事。方法的优势在于所有阶段都是分离的,因此您可以独立地提出问题并寻求每个阶段的帮助。

public static String findSimilarity(String word, List<String> candidatesList) {

    // Populating the set with distinct values of the input terms
    Set<String> candidates = candidatesList.stream()
            .map(String::toLowerCase)
            .map(term -> Arrays.stream(term.split(" ")).sorted().collect(Collectors.joining(" "))) // eliminates permutations
            .collect(Collectors.toSet());

    Map<String, Integer> cache = new ConcurrentHashMap<>();

    return candidates.parallelStream()
            .map(String::trim)
                    // add more mappers if needed
            .filter(s -> !s.equalsIgnoreCase(word))
                    // add more filters if needed
            .min((a, b) -> Integer.compare(
                    cache.computeIfAbsent(a, k -> getLevenshteinDistance(word, k)),
                    cache.computeIfAbsent(b, k -> getLevenshteinDistance(word, k))))
            .get(); // get the closest match
}