比较2个相似字符串的最佳方法?

时间:2017-08-08 00:47:20

标签: javascript node.js

原始问题:

  

我有很多不同名称的产品,我有两种变体   我需要比较的名字(基本上找出这两个字符串   是相同的产品)。我不想要任何虚假的旗帜,没有人有   关于如何实现这一目标的建议?

     

以下是产品示例:

     

Canon 50mm f/1.2L vs Canon EF 50mm f/1.2L USM Lens

     

还有其他变化,但这将是典型的差异。   是否有任何简单的功能我可以实现以获得一定的   回答?我唯一能想到的就是分裂弦乐   比较并说出x是否匹配a,b或c。

我原来的问题有点模糊。最终目标是能够比较两个字符串,看看它们有多相似 - 例如0%,50%或100%相似。在这种情况下,我使用不同来源的镜头产品,他们使用相似的名称 - 但我没有产品sku / id进行适当的比较。

string score plugin解决了我的问题,提供了similar这些产品的价值。

2 个答案:

答案 0 :(得分:1)

你必须考虑如何通过阅读它们来识别两个字符串本身是否是同一个产品。

仅基于您提供的示例,似乎告诉表示产品的两个字符串的方式是相同的,如果较短的字符串中包含的每个单词(由空格分隔的标记)都包含在较长的字符串中。

您可能还想忽略大写。

这样的东西应该适用于基本用法:

const tokens = s => s.toLowerCase().split(/\s+/g);

const sameProducts = (s1, s2) => {

  const s1Tokens = tokens(s1);
  const s2Tokens = tokens(s2);

  const [shorterTokens, longerTokens] = s1Tokens.length > s2Tokens.length
    ? [s2Tokens, s1Tokens]
    : [s1Tokens, s2Tokens];

  return shorterTokens.every(st => longerTokens.includes(st));
}

console.log(
  sameProducts(
    'Canon 50mm f/1.2L',
    'Canon EF 50mm f/1.2L USM Lens'
  )
)

此代码具有二次时间复杂度,因为最昂贵的操作意味着,对于较短字符串中的每个标记,您必须遍历较长字符串中的每个标记。

一个简单的优化是从较长的字符串构建Set<token>。这会使操作成为线性,因为搜索集合是O(1)

const tokens = s => s.toLowerCase().split(/\s+/g);

const sameProducts = (s1, s2) => {

  const s1Tokens = tokens(s1);
  const s2Tokens = tokens(s2);

  const [shorterTokens, longerTokens] = s1Tokens.length > s2Tokens.length
    ? [s2Tokens, s1Tokens]
    : [s1Tokens, s2Tokens];

  const longerTokensSet = longerTokens.reduce((s, t) => {
    s.add(t);
    return s;
  }, new Set());

  return shorterTokens.every(st => longerTokensSet.has(st));
}

console.log(
  sameProducts(
    'Canon 50mm f/1.2L',
    'Canon EF 50mm f/1.2L USM Lens'
  )
)

现在您必须考虑,所有令牌必须匹配吗?也许只有与品牌和焦距相对应的标记必须匹配。

如果是这种情况,您可能还希望在解析它们时验证这两个字符串,如果产品字符串无效,则立即返回false

这是一个粗略的想法:

const productSet = new Set(['canon'])
const focalLengthsSet = new Set(['50mm']);

const isMeaningful = t => productSet.has(t) || focalLengthsSet.has(t);

const meaningfulTokens = s => s.toLowerCase().split(/\s+/g).filter(isMeaningful);

const validTokens = (tokens, s) => {
  const valid = tokens.length === 2;  // <-- could do better validation here
  console.assert(valid, `Missing token(s) in ${s}`);
  return valid;
}

const sameProducts = (s1, s2) => {

  const s1Tokens = meaningfulTokens(s1);
  if (!validTokens(s1Tokens, s1)) { return false; }
  
  const s2Tokens = meaningfulTokens(s2);
  if (!validTokens(s2Tokens, s2)) { return false; }

  const [shorterTokens, longerTokens] = s1Tokens.length > s2Tokens.length
    ? [s2Tokens, s1Tokens]
    : [s1Tokens, s2Tokens];

  const longerTokensSet = longerTokens.reduce((s, t) => {
    s.add(t);
    return s;
  }, new Set());

  return shorterTokens.every(st => longerTokensSet.has(st));
}

console.log(
  sameProducts(
    'Canon 50mm f/1.3',
    'Canon EF 50mm f/1.2'
  )
)

console.log(
  sameProducts(
    'Canon 50mm f/1.3',
    'Canon EF f/1.2' // <-- missing focal length
  )
)

现在您可以考虑每个焦距是否与每个产品相对应,还是更具产品特色?

令牌是否包含明确依赖于先前匹配的令牌的逻辑?

以上所有内容都只是您可以使用的基本方法和技术,但实际解决方案在很大程度上取决于您的具体情况。

测量字符串相似度的常用算法称为Levenstein distance

  

两个单词之间的Levenshtein距离是将一个单词更改为另一个单词所需的单个字符编辑(插入,删除或替换)的最小数量。

如果您编辑距离阈值足够严格(尽管这可能会提供误报),或者您甚至可以考虑拼写错误的产品,例如在比较单个令牌时确保它们位于其中,此算法可能允许您直接匹配字符串彼此之间的特定编辑距离。

答案 1 :(得分:1)

在生物信息学中,我相信其他领域,这种模式匹配/搜索算法称为fuzzy search

有一个名为string_score的nodeJS模块。基本上你用2个字符串来提供API,它会返回一个与它们有多相似的分数。

示例:

var test = require('string_score');

var match_percent = "Canon EF 50mm f/1.2L USM Lens".score("Canon 50mm f/1.2L");
console.log("Match score= " + match_percent);

<强>输出:

  

比赛得分= 0.7938133874239354

使用分数作为比较基线。您可以说它是否具有装备或超过80的分数,那么它就是匹配。

更多示例:

&#13;
&#13;
var score = 0;

score = "hello world".score("he");        
console.log("Match score => " + score);

score = "hello world".score("hel");
console.log("Match score => " + score);

score = "hello world".score("hell");
console.log("Match score => " + score);

score = "hello world".score("hello");
console.log("Match score => " + score);
&#13;
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/string_score/0.1.10/string_score.min.js"></script>
&#13;
&#13;
&#13;

<强>参考文献:

String_score:https://github.com/joshaven/string_score