字符串相似度算法(比Levenshtein和similar_text更好的)?Php,Js

17

我该在哪里找到比levenshtein()和php similar_text()方法更准确地价值于错位字符的算法?

示例:

similar_text('jonas', 'xxjon', $similar); echo $similar; // returns 60
similar_text('jonas', 'asjon', $similar); echo $similar; // returns 60 <- although more similar!
echo levenshtein('jonas', 'xxjon'); // returns 4
echo levenshtein('jonas', 'asjon'); // returns 4  <- although more similar!

/ Jonas

-->

/ Jonas


相对于“jonas”,levenshtein针对“nojsa”和“nojxx”给出了什么结果? - Tim
请问您在寻找具有更精细分级的算法方面的最终目标是什么?您的示例使用了专有名词。您正在处理的真实数据是否涉及名称或单词? - Tim
@Tim:我实际上正在寻找一种在教育游戏环境中处理/测量相似性的方法。假设一个学生的任务是从一个池中选择物品,并按特定顺序将这些物品放置(按字母表顺序或其他方式进行排序)。然后,我需要一种方法来衡量学生答案与正确答案之间的相似度... - Cambiata
5个回答

15
这是我想出的一个解决方案,基于Tim建议的比较连续字符顺序的方法。部分结果如下:
  • jonas / jonax : 0.8
  • jonas / sjona : 0.68
  • jonas / sjonas : 0.66
  • jonas / asjon : 0.52
  • jonas / xxjon : 0.36
我知道它不是完美的,并且可能需要优化,但它似乎产生了我想要的结果... 其中一个弱点是当字符串长度不同时,交换值会产生不同的结果...
static public function string_compare($str_a, $str_b) 
{
    $length = strlen($str_a);
    $length_b = strlen($str_b);

    $i = 0;
    $segmentcount = 0;
    $segmentsinfo = array();
    $segment = '';
    while ($i < $length) 
    {
        $char = substr($str_a, $i, 1);
        if (strpos($str_b, $char) !== FALSE) 
        {               
            $segment = $segment.$char;
            if (strpos($str_b, $segment) !== FALSE) 
            {
                $segmentpos_a = $i - strlen($segment) + 1;
                $segmentpos_b = strpos($str_b, $segment);
                $positiondiff = abs($segmentpos_a - $segmentpos_b);
                $posfactor = ($length - $positiondiff) / $length_b; // <-- ?
                $lengthfactor = strlen($segment)/$length;
                $segmentsinfo[$segmentcount] = array( 'segment' => $segment, 'score' => ($posfactor * $lengthfactor));
            } 
            else 
            {
                $segment = '';
                $i--;
                $segmentcount++;
            } 
        } 
        else 
        {
            $segment = '';
            $segmentcount++;
        }
        $i++;
    }   

    // PHP 5.3 lambda in array_map      
    $totalscore = array_sum(array_map(function($v) { return $v['score'];  }, $segmentsinfo));
    return $totalscore;     
}

你能解释一下你的函数内部逻辑吗?特别是,我不理解$segmentInfo['segment'] - 它只是用于调试吗? - user1122069

6
请注意使用 string_compare

ivanov ivan / ivanov ivan :1 OK!

ivanov ivan2 / ivanov ivan :1 o_O

ivanov ivan / ivanov i :1.1363636363636 OMG!


6

除了levenshtein()和similar_text()之外,还有:

soundex():返回单词的四个字符的soundex键,这应该与任何类似发音的单词的键相同。
metaphone():类似于soundex,可能更适合您。它比soundex()更准确,因为它知道英语发音的基本规则。metaphone生成的键长度可变。


谢谢,马克!嗯...它们都是用于计算声音相似度的算法,在我的情况下可能会产生误导 - 尚未测试,但可能会导致结果如“chou”与“show”非常接近,而字符内容却非常不同。 - Cambiata

4

我发现Jaro Winkler距离在处理拼写错误和字符串之间的小差异时也很有效。我修改了这段代码使其面向对象:

class StringCompareJaroWinkler 
{
    public function compare($str1, $str2)
    {
        return $this->JaroWinkler($str1, $str2, $PREFIXSCALE = 0.1 );
    }

    private function getCommonCharacters( $string1, $string2, $allowedDistance ){

      $str1_len = mb_strlen($string1);
      $str2_len = mb_strlen($string2);
      $temp_string2 = str_split($string2);

      $commonCharacters='';
      for( $i=0; $i < $str1_len; $i++){

        $noMatch = True;
        // compare if char does match inside given allowedDistance
        // and if it does add it to commonCharacters
        for( $j= max( 0, $i-$allowedDistance ); $noMatch && $j < min( $i + $allowedDistance + 1, $str2_len ); $j++){
          if( $temp_string2[$j] == $string1[$i] ){
            $noMatch = False;
        $commonCharacters .= $string1[$i];
        $temp_string2[$j] = '';
          }
        }
      }
      return $commonCharacters;
    }

    private function Jaro( $string1, $string2 ){

      $str1_len = mb_strlen( $string1 );
      $str2_len = mb_strlen( $string2 );

      // theoretical distance
      $distance = (int) floor(min( $str1_len, $str2_len ) / 2.0); 

      // get common characters
      $commons1 = $this->getCommonCharacters( $string1, $string2, $distance );
      $commons2 = $this->getCommonCharacters( $string2, $string1, $distance );

      if( ($commons1_len = mb_strlen( $commons1 )) == 0) return 0;
      if( ($commons2_len = mb_strlen( $commons2 )) == 0) return 0;
      // calculate transpositions
      $transpositions = 0;
      $upperBound = min( $commons1_len, $commons2_len );
      for( $i = 0; $i < $upperBound; $i++){
        if( $commons1[$i] != $commons2[$i] ) $transpositions++;
      }
      $transpositions /= 2.0;
      // return the Jaro distance
      return ($commons1_len/($str1_len) + $commons2_len/($str2_len) + ($commons1_len - $transpositions)/($commons1_len)) / 3.0;

    }

    private function getPrefixLength( $string1, $string2, $MINPREFIXLENGTH = 4 ){

      $n = min( array( $MINPREFIXLENGTH, mb_strlen($string1), mb_strlen($string2) ) );

      for($i = 0; $i < $n; $i++){
        if( $string1[$i] != $string2[$i] ){
          // return index of first occurrence of different characters 
          return $i;
        }
      }
      // first n characters are the same   
      return $n;
    }

    private function JaroWinkler($string1, $string2, $PREFIXSCALE = 0.1 ){

      $JaroDistance = $this->Jaro( $string1, $string2 );
      $prefixLength = $this->getPrefixLength( $string1, $string2 );
      return $JaroDistance + $prefixLength * $PREFIXSCALE * (1.0 - $JaroDistance);
    }
}

$jw = new StringCompareJaroWinkler();
echo $jw->compare("jonas","asjon");

这确实有效,谢谢!唯一的问题是速度有点慢。处理2万个字符串大约需要1.5秒的时间。 - rockstardev

1
@Tim:我正在寻找一种处理/测量教育游戏背景下相似性的方法。假设学生的任务是从一个池中选择对象,并按特定顺序将这些对象放置(按字母表顺序或其他方式进行排序)。然后,我需要一种方法来衡量学生答案与正确答案之间的相似度。
计算单词中字符顺序(即其拼写)的正确程度的算法可能与测量列表中单词正确顺序的算法非常不同。拼写算法处理省略、重复或转位可能不太适用于您的用例。
如果您事先知道元素的顺序并且也知道元素的数量,则可以简单地循环遍历答案并将位置处的值与正确位置处的值进行比较,从而得出百分比正确率。但这将是一种粗略的度量方法,并且会误导人,因为如果您的游戏目标是测试玩家是否理解了字母排序,而玩家恰好将第一个单词搞错了,即使单词按照正确的字母顺序排列,每个单词都可能在错误的位置上:
      banana
      blackberry
      blueberry
      cherry
      fig
      grapefruit
      orange
      pear
      persimmon
      raspberry
      apple

所以,在我们的假设情况下,你可以做些什么来提高测量的准确性呢?循环遍历游戏玩家的答案列表,查看答案值是否紧随正确的单词之后;每次一个单词紧随着正确的单词出现,你就给游戏玩家加一分。以上面的列表为例,游戏玩家将获得10分中的9分,这个分数确实准确地反映了游戏玩家对字母排序规则的理解程度。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接