【问题标题】:Comparing arrays of strings for similarity比较字符串数组的相似性
【发布时间】:2011-09-26 01:21:03
【问题描述】:

我可以使用数百个 JSON 字符串。每一个都包含一个由 15-20 个单词组成的数组,这些单词按预先确定的权重排序。如果值得注意的话,这个权重是这些词在某个文本块中出现的次数。在这种结构的单词数组之间找到相似度的最佳方法是什么?

我想到的第一个想法是创建所有单词的数字哈希值,然后基本上比较这些值以确定相似度。我不是很成功,因为非常相似的字符串的结果哈希值不是很接近。在对字符串比较算法进行了一些研究之后,我来到了 Stackoverflow,希望得到更多的指导。提前致谢,如果您需要有关问题的更多详细信息,请告诉我。

编辑 1:澄清我想要做的事情:我想根据每个数组的单词来确定两个数组的相似程度。我还想考虑每个单词在每个数组中的权重。例如:

var array1 = [{"word":"hill","count":5},{"word":"head","count":5}];
var array2 = [{"word":"valley","count":7},{"word":"head","count":5}];
var array3 = [{"word":"head", "count": 6}, {"word": "valley", "count": 5}];
var array4 = [{"word": "valley", "count": 7}, {"word":"head", "count": 5}];

在该示例中,数组 4 和数组 2 比数组 2 和数组 3 更相似,因为即使它们具有相同的单词,它们在数组 4 和 2 中的权重也是相同的。我希望这使得它更容易理解。提前致谢。

【问题讨论】:

  • 所以你有 N 个数组,每个数组有 Nm 个单词,你想确定到底是什么?
  • 我编辑了我的原始帖子并进行了一些澄清。希望对您有所帮助并感谢您的关注。
  • 什么是头和哈德或头和洞穴更相似?
  • 嗨 Itay,我对两个词的相似之处不感兴趣,我对两个数组的相似之处感兴趣,因为它们共享一些词。
  • 您是在寻找一种算法来确定两个字符串的整体相似性,还是在寻找如何遍历所有数组来进行比较?还是两者兼而有之?

标签: javascript arrays string comparison similarity


【解决方案1】:

我认为您想要的是“cosine similarity”,您可能还想查看vector space models。如果你是在 Java 中编码,你可以使用开源的S-space 包。

(10 月 31 日添加)向量的每个元素都是一个特定字符串的计数。您只需要将字符串数组转换为此类向量。在你的例子中,你有三个词——“山”、“头”、“谷”。如果您的向量按该顺序排列,则与数组对应的向量将是

// array: #hill, #head, #valley
array1:  {5,     5,     0}
array2:  {0,     5,     7}
array3:  {0,     6,     5}
array4:  {0,     5,     7}

【讨论】:

  • 感谢您的建议。尽管这是非常有用且有趣的材料,但在这种情况下,我对比较字符串本身的相似性不感兴趣。我只关心它们是否相同。在这种情况下,我正在比较字符串数组的相似性。
  • @Xavier - 是的,这就是余弦相似度的作用。向量的每个元素都是一个特定字符串的计数。您只需要将字符串数组转换为这样的向量。在你的例子中,你有三个词——“山”、“头”、“谷”。如果您的向量按该顺序排列,则与 array1 对应的向量将为 {5, 5, 0}。
  • 有趣,kc2001。谢谢你回到我身旁。我还没有完全理解,我不得不承认。在您解释的情况下,仅包含计数的向量如何帮助我比较数组?换句话说,该向量中的哪里是包含实际字符串的信息,而不仅仅是字符串的计数?我看到一些研究网络的例子,他们制作了一个字符串字母表 [abcde],然后是一个基于两个字符串之间的字符联合的向量。这两个向量然后使用余弦相似度进行比较你在这里建议类似的方法吗?
  • 我想我现在明白了。我必须在数组之间执行联合操作,其中每个元素都是一个单词。一旦我有了一个包含两个数组中所有单词的向量,然后我将每个单词数组转换为它,方法是将不在数组中的单词替换为 0,否则使用计数。完成后,我应该有两个向量可以在其上执行余弦相似度。这是正确的方法吗?
  • @Xavier - 是的,基本上就是这样。
【解决方案2】:

鉴于每个数组都必须与每个其他数组进行比较,您正在按照 ∑(n-1) 乘以每个数组中“单词”的平均数量的方式进行大量处理。您需要存储每次比较的分数,然后对其有所了解。

例如

var array1 = [{"word":"hill","count":5},{"word":"head","count":5}];
var array2 = [{"word":"valley","count":7},{"word":"head","count":5}];
var array3 = [{"word":"head", "count": 6}, {"word": "valley", "count": 5}];
var array4 = [{"word": "valley", "count": 7}, {"word":"head", "count": 5}];

// Comparison score is summed product of matching word counts
function compareThings() {

  var a, b, i = arguments.length,
      j, m, mLen, n, nLen;
  var word, score, result = [];

  if (i < 2) return;

  // For each array
  while (i--) {
    a = arguments[i];
    j = i;

    // Compare with every other array
    while (j--) {
      b = arguments[j];
      score = 0;

      // For each word in array
      for (m=0, mLen = b.length; m<mLen; m++) {
        word = b[m].word

        // Compare with each word in other array
        for (n=0, nLen=a.length; n<nLen; n++) {

          // Add to score
          if (a[n].word == word) {
            score += a[n].count * b[m].count;
          }
        }
      }

      // Put score in result
      result.push(i + '-' + j + ':' + score);
    }
  }
  return result;
}

var results = compareThings(array1, array2, array3, array4);

alert('Raw results:\n' + results.join('\n'));
/*
Raw results:
3-2:65
3-1:74
3-0:25
2-1:65
2-0:30
1-0:25
*/

results.sort(function(a, b) {
  a = a.split(':')[1];
  b = b.split(':')[1];
  return b - a;
});

alert('Sorted results:\n' + results.join('\n'));
/*
Sorted results:
3-1:74
3-2:65
2-1:65
2-0:30
3-0:25
1-0:25
*/

所以 3-1(array4 和 array2)得分最高。幸运的是,比较只需要一种方式,您不必将 a 与 b 以及 b 与 a 进行比较。

【讨论】:

  • 感谢 RobG。为什么您通过将权重相乘而不是像此处提供的其他建议中那样减去它们来计算相似度的任何具体原因?我喜欢它,因为它对我测试的案例做了我想要的,但好像这个数字是任意的和不可预测的。例如,如果您有两个数组,其中有一个相同的单词,但其中一个数组的权重很大,那么它会与具有更多相似词但权重较小的数组更相似。不过,这是一个好的开始,我感谢您的努力。
  • 我想是否添加或乘以“权重”取决于您的背景。在我所做的统计分析工作中,权重就像概率,所以值乘以它们。一些现实世界的例子是航行障碍(比赛的长度和条件不同,因此经过的时间乘以障碍)和调整测量控制网络,其中每个测量具有不同的精度(例如 +-10 毫米),因此具有不同的重量在调整中。
  • 我明白,这当然取决于我想采取的方法。谢谢,RobG。
【解决方案3】:

这是一个尝试。该算法不是很聪明(差异 > 20 与没有相同的单词相同),但可能是一个有用的开始:

var wordArrays = [
    [{"word":"hill","count":5},{"word":"head","count":5}]
  , [{"word":"valley","count":7},{"word":"head","count":5}]
  , [{"word":"head", "count": 6}, {"word": "valley", "count": 5}]
  , [{"word": "valley", "count": 7}, {"word":"head", "count": 5}]
]

function getSimilarTo(index){
    var src = wordArrays[index]
      , values

    if (!src) return null;

    // compare with other arrays
    weighted = wordArrays.map(function(arr, i){
        var diff = 0
        src.forEach(function(item){
            arr.forEach(function(other){
                if (other.word === item.word){
                    // add the absolute distance in count
                    diff += Math.abs(item.count - other.count)
                } else {
                    // mismatches
                    diff += 20
                }
            })
        })
        return {
            arr   : JSON.stringify(arr)
          , index : i
          , diff  : diff
        }
    })

    return weighted.sort(function(a,b){
        if (a.diff > b.diff) return 1
        if (a.diff < b.diff) return -1
        return 0
    })
}

/*
getSimilarTo(3)
[ { arr: '[{"word":"valley","count":7},{"word":"head","count":5}]',
    index: 1,
    diff: 100 },
  { arr: '[{"word":"valley","count":7},{"word":"head","count":5}]',
    index: 3,
    diff: 100 },
  { arr: '[{"word":"head","count":6},{"word":"valley","count":5}]',
    index: 2,
    diff: 103 },
  { arr: '[{"word":"hill","count":5},{"word":"head","count":5}]',
    index: 0,
    diff: 150 } ]
*/

【讨论】:

    【解决方案4】:

    在尝试比较之前按单词对数组进行排序。完成后,比较两个数组将需要准确地遍历每个数组。

    对数组排序后,这里是一个比较算法(psuedo-java):

    
    int compare(array1, array2)
    {
      returnValue = 0;
      array1Index = 0
      array2Index = 0;
    
      while (array1Index < array1.length)
      {
        if (array2Index < array2.length)
        {
          if (array1[array1Index].word == array2[array2Index].word) // words match.
          {
            returnValue += abs(array1[array1Index].count - array2[array2Index].count);
            ++array1Index;
            ++array2Index;
          }
          else // account for the unmatched array2 word.
          {
            // 100 is just a number to give xtra weight to unmatched numbers.
            returnValue += 100 + array2[array2Index].count;
            ++array2Index;
          }
        }
        else // array2 empty and array1 is not empty.
        {
          // 100 is just a number to give xtra weight to unmatched numbers.
          returnValue += 100 + array1[array1Index].count;
        }
      }
    
      // account for any extra unmatched array 2 values.
      while (array2Index < array2.length)
      {
          // 100 is just a number to give xtra weight to unmatched numbers.
          returnValue += 100 + array2[array2Index].count;
      }
    
      return returnValue;
    }
    
    

    【讨论】:

    • DwB,感谢您的回答!您的方法很有趣,因为它允许算法只遍历每个数组一次。但我在这个实现中没有看到的是,当您在 array2 中找不到单词时会发生什么?您将继续执行内部 else 语句,直到第一个 if 条件失败并且您在没有找到匹配项的情况下退出 while 循环,即使您没有尝试过 array1 中的任何其他单词。实际上,这种比较在这种情况下会失败,因为它将停留在无限循环中。不过,感谢您在这一点上的建议,这是一个非常有帮助的开始。
    猜你喜欢
    • 1970-01-01
    • 2010-10-31
    • 2010-10-19
    • 1970-01-01
    • 2011-08-17
    • 2011-10-20
    • 1970-01-01
    • 1970-01-01
    • 2021-05-16
    相关资源
    最近更新 更多