java - 找到最相似值的有效方法

标签 java string similarity

我有一个值,例如“颜色”,以及一个字符串列表:{颜色,颜色,主颜色,主颜色,主题,品牌,主题.....等}

我想获得最相似的字符串,除了搜索到的字符串本身。在此示例中,期望获得 Color。 (不是颜色)

我正在对列表进行排序 我使用以下规则并对规则进行排名:

  1. 过滤相同的值
  2. 检查大小写
  3. 删除空格。修剪
  4. 使用编辑距离
  5. 字符串顺序:主色 = Color Main
  6. 检查缩写词:HP - 惠普

检查 1000 名相关候选人的名单需要花费大量时间。而且我还有很多候选人需要检查。

还有其他有效的方法吗?

原始代码:

public static List findSimilarity(String word, List candidates) {
    List recommendations = new ArrayList();
    if (!word.equals("")) {
        for (String candidate : candidates) {
            if (!word.equals(candidate)) { //1. same token , lower/upper cases , ignore white spaces
                if (StringUtils.deleteWhitespace(word).equalsIgnoreCase(StringUtils.deleteWhitespace(candidate))) {
                    recommendations.add(candidate);
                }
                //2. same tokens diff order
                else if (candidate.split(" ").length == word.split("     ").length) {
                    String[] candidatearr = candidate.split(" ");
                    String[] wordarr = word.split(" ");
                    boolean status = true;
                    SortIgnoreCase icc = new SortIgnoreCase();
                    Arrays.sort(candidatearr, icc);
                    Arrays.sort(wordarr, icc);
                    for (int i = 0; i < candidatearr.length; i++) {
                        if (!(candidatearr[i] == null ? wordarr[i] == null : wordarr[i].equalsIgnoreCase(candidatearr[i])))
                            status = false;
                    }

                    if (status) {
                        recommendations.add(candidate);
                    }
                }
            }
        }
        //3. distance between words
        if (recommendations.size() == 0) {
            for (String candidate : candidates) {
                if (!word.equals(candidate)) {
                    String[] candidatearr = candidate.split(" ");
                    String[] wordarr = word.split(" ");
                    //check for acronym
                    if ((wordarr.length == 1 && candidatearr.length > 1) || (wordarr.length > 1 && candidatearr.length == 1)) {
                        String acronym = "";
                        if (wordarr.length > candidatearr.length) {
                            for (String tmp : wordarr) {
                                if (!tmp.equals("")) {
                                    acronym = acronym + tmp.substring(0, 1);
                                }
                            }

                            if (acronym.equalsIgnoreCase(candidatearr[0])) {
                                recommendations.add(candidate);
                            }
                        } else {
                            for (String tmp : candidatearr) {
                                if (!tmp.equals("")) {
                                    acronym = acronym + tmp.substring(0, 1);
                                }
                            }

                            if (acronym.equalsIgnoreCase(wordarr[0])) {
                                recommendations.add(candidate);
                            }
                        }
                    }
                }
            }
        }

        if (recommendations.size() == 0) {
            for (String candidate : candidates) {
                if (!word.equals(candidate)) {
                    int dist = 0;
                    String check = "";
                    if (word.length() > candidate.length()) {
                        check = candidate;
                    } else {
                        check = word;
                    }
                    if (check.length() <= 3) {
                        dist = 0;
                    } else if (check.length() > 3 && check.length() <= 5) {
                        dist = 1;
                    } else if (check.length() > 5) {
                        dist = 2;
                    }

                    if (StringUtils.getLevenshteinDistance(word, candidate) <= dist) {
                        //if(Levenshtein.distance(word,candidate) <= dist){
                        recommendations.add(candidate);
                    }
                }
            }
        }

        if (recommendations.size() == 0) {
            for (String candidate : candidates) {
                if (!word.equals(candidate)) {
                    String[] candidatearr = candidate.split(" ");
                    String[] wordarr = word.split(" ");

                    for (String cand : candidatearr) {
                        for (String wor : wordarr) {
                            if (cand.equals(wor) && cand.length() > 4) {
                                recommendations.add(candidate);

                            }
                        }
                    }
                }
            }//for
            if (recommendations.size() > 4) {
                recommendations.clear();
            }
        }

        //4. low priority - starts with
        if (recommendations.size() == 0) {
            for (String candidate : candidates) {
                if (!word.equals(candidate)) {
                    if (candidate.startsWith(word) || word.startsWith(candidate)) {
                        recommendations.add(candidate);
                    }
                }
            }
            if (recommendations.size() > 4) {
                recommendations.clear();
            }
        }

        //5. low priority - contain word
        if (recommendations.size() == 0) {
            for (String candidate : candidates) {
                if (!word.equals(candidate)) {
                    if (candidate.contains(word) || word.contains(candidate)) {
                        recommendations.add(candidate);
                    }
                }
            }
            if (recommendations.size() > 4) {
                recommendations.clear();
            }
        }
    }
    return recommendations;
}

谢谢, 米。

最佳答案

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

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

我使用各种列表大小对随机整数列表进行排序进行了测试,实际调用 compare() 的次数非常接近 n log2 n,因此对于大约 1000 个字符串的列表,速度会快大约 10 倍,因为 log2(1000) 大约是 10。

您可以通过不排序,而是仅获取指定相同比较器的最小项来进一步提高性能。

另一个改进是通过使用 Set(强制唯一性)来保存候选值,从而避免 distinct() 调用(该调用相对昂贵)。

如果可以的话,请使用已训练且小写的值填充候选值,这样就可以避免每次运行时都进行修剪、小写和小写。对输入进行相同的操作,这样您就可以使用 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 个随机候选,此代码的执行时间约为 50 毫秒,对于 100 万个候选,该代码的执行时间约为 1 秒。

关于java - 找到最相似值的有效方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34917913/

相关文章:

java - hadoop: reducer 的数量保持不变 4

java - 关于在 Swing 页面上添加容器的问题

MySQL 将行加载到字符串内容

c++ - 在C++中将字符串转换为不带空格的字符数组

opencv - 为什么cv.matchShape并不像翻译所要求的那样不变?

nlp - 我可以使用什么强大的算法实现来执行两个输入的短语相似度?

vba - 运行vb代码计算相似度时定义首字母缩略词

java - 将日期格式转换为另一种格式时出错

Java:String intern() 和 StringPool 究竟是如何工作的?

swift - Kotlin 相当于 %@ 在 swift