web-dev-qa-db-ja.com

Similar_textはどのように機能しますか?

Similar_text関数を見つけて、いじくり回していましたが、出力の割合はいつも驚きます。以下の例を参照してください。

php:similar_text()で言及されているように使用されるアルゴリズムに関する情報を見つけようとしました。ドキュメント

<?php
$p = 0;
similar_text('aaaaaaaaaa', 'aaaaa', $p);
echo $p . "<hr>";
//66.666666666667
//Since 5 out of 10 chars match, I would expect a 50% match

similar_text('aaaaaaaaaaaaaaaaaaaa', 'aaaaa', $p);
echo $p . "<hr>";
//40
//5 out of 20 > not 25% ?

similar_text('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'aaaaa', $p);
echo $p . "<hr>"; 
//9.5238095238095 
//5 out of 100 > not 5% ?


//Example from PHP.net
//Why is turning the strings around changing the result?

similar_text('PHP IS GREAT', 'WITH MYSQL', $p);
echo $p . "<hr>"; //27.272727272727

similar_text('WITH MYSQL', 'PHP IS GREAT', $p);
echo $p . "<hr>"; //18.181818181818

?>

誰がこれが実際にどのように機能するかを説明できますか?

更新:

コメントのおかげで、パーセンテージは実際に同様の文字の数* 200/length1 + lenght 2を使用して計算されることがわかりました

Z_DVAL_PP(percent) = sim * 200.0 / (t1_len + t2_len);

そのため、percenatgesが予想よりも高い理由を説明しています。 95個のうち5個の文字列を使用すると、10個になるため、使用できます。

similar_text('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'aaaaa', $p);
echo $p . "<hr>"; 
//10
//5 out of 95 = 5 * 200 / (5 + 95) = 10

しかし、私はまだPHPが文字列の向きを変えると異なる結果を返します。dfsqが提供するJSコードはこれを行いません。PHP次の行で違いを見つけることしかできませんが、私はACプログラマーではありません。

JSの場合:

for (l = 0;(p + l < firstLength) && (q + l < secondLength) && (first.charAt(p + l) === second.charAt(q + l)); l++);

PHPの場合:(php_similar_str関数)

for (l = 0; (p + l < end1) && (q + l < end2) && (p[l] == q[l]); l++);

ソース:

/* {{{ proto int similar_text(string str1, string str2 [, float percent])
   Calculates the similarity between two strings */
PHP_FUNCTION(similar_text)
{
  char *t1, *t2;
  zval **percent = NULL;
  int ac = ZEND_NUM_ARGS();
  int sim;
  int t1_len, t2_len;

  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|Z", &t1, &t1_len, &t2, &t2_len, &percent) == FAILURE) {
    return;
  }

  if (ac > 2) {
    convert_to_double_ex(percent);
  }

  if (t1_len + t2_len == 0) {
    if (ac > 2) {
      Z_DVAL_PP(percent) = 0;
    }

    RETURN_LONG(0);
  }

  sim = php_similar_char(t1, t1_len, t2, t2_len);

  if (ac > 2) {
    Z_DVAL_PP(percent) = sim * 200.0 / (t1_len + t2_len);
  }

  RETURN_LONG(sim);
}
/* }}} */ 


/* {{{ php_similar_str
 */
static void php_similar_str(const char *txt1, int len1, const char *txt2, int len2, int *pos1, int *pos2, int *max)
{
  char *p, *q;
  char *end1 = (char *) txt1 + len1;
  char *end2 = (char *) txt2 + len2;
  int l;

  *max = 0;
  for (p = (char *) txt1; p < end1; p++) {
    for (q = (char *) txt2; q < end2; q++) {
      for (l = 0; (p + l < end1) && (q + l < end2) && (p[l] == q[l]); l++);
      if (l > *max) {
        *max = l;
        *pos1 = p - txt1;
        *pos2 = q - txt2;
      }
    }
  }
}
/* }}} */


/* {{{ php_similar_char
 */
static int php_similar_char(const char *txt1, int len1, const char *txt2, int len2)
{
  int sum;
  int pos1, pos2, max;

  php_similar_str(txt1, len1, txt2, len2, &pos1, &pos2, &max);

  if ((sum = max)) {
    if (pos1 && pos2) {
      sum += php_similar_char(txt1, pos1,
                  txt2, pos2);
    }
    if ((pos1 + max < len1) && (pos2 + max < len2)) {
      sum += php_similar_char(txt1 + pos1 + max, len1 - pos1 - max,
                  txt2 + pos2 + max, len2 - pos2 - max);
    }
  }

  return sum;
}
/* }}} */

Javascriptのソース: javascriptへの同様のテキストポート

54
Hugo Delsing

実際、関数はパラメーターの順序に応じて異なるロジックを使用しているように見えます。プレイには2つのことがあると思います。

まず、この例を参照してください。

echo similar_text('test','wert'); // 1
echo similar_text('wert','test'); // 2

「param1の個別のcharがparam2で見つかった回数」をテストしているように見えるため、paramsを入れ替えると結果が異なります。 bug として報告されており、「正常に動作している」としてクローズされています。

さて、上記はPHPとjavascriptの実装の両方でsameです-パラメーターの順序が影響するため、 JSコードはこれを実行しませんが、これは間違っています。

第二-正しくないと思われるのは、MYSQL/PHP Wordの例です。これにより、javascriptバージョンはparamsの順序に関係なく3を与えますが、PHPは2と3を与えます(そして、そのため、パーセンテージも等しく異なります)。今、フレーズ "PHP IS GREAT "および" WITH MYSQL "には、H、I、S、およびT、それぞれ1つ、および空のスペースに1つを加えたものに関係なく、共通の5文字が必要です。文字、「H」、「」、および「S」なので、順序を見ると、正解は両方の3つの方法になっているはずです。Cコードを実行可能なバージョンに変更し、出力を追加して、そこに起こっている( コードパッドリンク ):

#include<stdio.h>

/* {{{ php_similar_str
 */
static void php_similar_str(const char *txt1, int len1, const char *txt2, int len2, int *pos1, int *pos2, int *max)
{
  char *p, *q;
  char *end1 = (char *) txt1 + len1;
  char *end2 = (char *) txt2 + len2;
  int l;

  *max = 0;
  for (p = (char *) txt1; p < end1; p++) {
    for (q = (char *) txt2; q < end2; q++) {
      for (l = 0; (p + l < end1) && (q + l < end2) && (p[l] == q[l]); l++);
      if (l > *max) {
        *max = l;
        *pos1 = p - txt1;
        *pos2 = q - txt2;
      }
    }
  }
}
/* }}} */


/* {{{ php_similar_char
 */
static int php_similar_char(const char *txt1, int len1, const char *txt2, int len2)
{
  int sum;
  int pos1, pos2, max;

  php_similar_str(txt1, len1, txt2, len2, &pos1, &pos2, &max);

  if ((sum = max)) {
    if (pos1 && pos2) {
      printf("txt here %s,%s\n", txt1, txt2);
      sum += php_similar_char(txt1, pos1,
                  txt2, pos2);
    }
    if ((pos1 + max < len1) && (pos2 + max < len2)) {
      printf("txt here %s,%s\n", txt1+ pos1 + max, txt2+ pos2 + max);
      sum += php_similar_char(txt1 + pos1 + max, len1 - pos1 - max,
                  txt2 + pos2 + max, len2 - pos2 - max);
    }
  }

  return sum;
}
/* }}} */
int main(void)
{
    printf("Found %d similar chars\n",
        php_similar_char("PHP IS GREAT", 12, "WITH MYSQL", 10));
    printf("Found %d similar chars\n",
        php_similar_char("WITH MYSQL", 10,"PHP IS GREAT", 12));
    return 0;
}

結果が出力されます:

txt here PHP IS GREAT,WITH MYSQL
txt here P IS GREAT, MYSQL
txt here IS GREAT,MYSQL
txt here IS GREAT,MYSQL
txt here  GREAT,QL
Found 3 similar chars
txt here WITH MYSQL,PHP IS GREAT
txt here TH MYSQL,S GREAT
Found 2 similar chars

したがって、最初の比較で、関数は 'H'、 ''、および 'S'を見つけましたが、 'T'を見つけず、3の結果を得たことがわかります。2番目の比較は 'I'および 'T'を見つけましたが、 「H」、「」、または「S」。したがって、2の結果が得られます。

これらの結果の理由は出力から見ることができます:アルゴリズムは、2番目の文字列に含まれる最初の文字列の最初の文字を取得し、それをカウントし、2番目の文字列からその前の文字を破棄します。それが中間の文字を見逃している理由であり、それが文字の順序を変更するときに違いを引き起こすものです。

そこで起こることは意図的であってもそうでないかもしれません。ただし、それはjavascriptバージョンの動作方法ではありません。 javascriptバージョンで同じものを印刷すると、次のようになります。

txt here: PHP, WIT
txt here: P IS GREAT,  MYSQL
txt here: IS GREAT, MYSQL
txt here: IS, MY
txt here:  GREAT, QL
Found 3 similar chars
txt here: WITH, PHP 
txt here: W, P
txt here: TH MYSQL, S GREAT
Found 3 similar chars

javaScriptバージョンが異なる方法でそれを行うことを示しています。 javascriptバージョンは、最初の比較で 'H'、 ''、および 'S'が同じ順序であり、2番目の比較でも同じ 'H'、 ''、および 'S'を見つけるということです。この場合、paramsの順序は重要ではありません。

JavascriptはPHP関数のコードを複製することを意図しているため、同じように動作する必要があるため、@ Khezと現在統合されている修正の分析に基づいてバグレポートを提出しました。

27
eis

これは実際には非常に興味深い質問でした。非常にやりがいのあるパズルを私に与えてくれてありがとう。

similar_textが実際にどのように機能するかを説明することから始めましょう。


同様のテキスト:アルゴリズム

再帰ベースの アルゴリズムの分割統治 です。最初に2つの入力間の最長共通文字列を見つけ、問題をその文字列の周りのサブセットに分割することで機能します。

質問で使用した例は、実際にはすべて実行されますアルゴリズムの1つの反復のみ。 1つの反復を使用しない唯一のものと異なる結果を与えるものは php.netコメント からのものです。

以下は、simple_textの背後にある主な問題を理解し、できればそれがどのように機能するかについての洞察を与える簡単な例です。


同様のテキスト:欠陥

_eeeefaaaaafddddd
ddddgaaaaagbeeee

Iteration 1:
Max    = 5
String = aaaaa
Left : eeeef and ddddg
Right: fddddd and geeeee
_

この欠陥がすでに明らかであることを願っています。 は、両方の入力文字列で最長一致文字列の左と右を直接チェックするだけです。この例

_$s1='eeeefaaaaafddddd';
$s2='ddddgaaaaagbeeee';

echo similar_text($s1, $s2).'|'.similar_text($s2, $s1);
// outputs 5|5, this is due to Iteration 2 of the algorithm
// it will fail to find a matching string in both left and right subsets
_

正直に言うと、このケースをどのように扱うべきかはわかりません。文字列で異なるのは2文字だけであることがわかります。しかし、eeeeddddは、2つの文字列の両端にあり、何が不明か [〜#〜] nlp [〜#〜] マニア 他の文学 専門家はこの特定の状況について言わなければなりません。


同様のテキスト:引数のスワッピングに関する一貫性のない結果

入力順序に基づいて発生したさまざまな結果は、上記のように実際に対数が動作する方法によるものでした。最後に、何が起こっているのかを説明します。

_echo similar_text('test','wert'); // 1
echo similar_text('wert','test'); // 2
_

最初のケースでは、反復は1つだけです。

_test
wert

Iteration 1:
Max    = 1
String = t
Left :  and wer
Right: est and 
_

空/ヌル文字列は再帰で0を返すため、反復は1つだけです。これでアルゴリズムが終了し、結果が得られます:1

ただし、2番目のケースでは、複数の反復に直面しています。

_wert
test

Iteration 1:
Max    = 1
String = e
Left : w and t
Right: rt and st
_

すでに長さ1の共通文字列があります。左側のサブセットのアルゴリズムは0で終了しますが、右側では一致します。

_rt
st

Iteration 1:
Max    = 1
String = t
Left : r and s
Right:  and 
_

これにより、新しい最終結果が得られます。2

この非常に有益な質問と、再びC++を使用する機会に感謝します。


同様のテキスト: JavaScript Edition

短い答えは:javascriptコードは正しいアルゴリズムを実装していません

_sum += this.similar_text(first.substr(0, pos2), second.substr(0, pos2));
_

明らかにそれはfirst.substr(0,pos1)であるべきです

注: JavaScriptコードは 以前のコミットeis で修正されました。ありがとう @ eis

分かりやすく!

27
Khez
first String = aaaaaaaaaa = 10 letters
second String = aaaaa = 5 letters

first five letters are similar
a+a
a+a
a+a
a+a
a+a
a
a
a
a
a


( <similar_letters> * 200 ) / (<letter_count_first_string> + <letter_count_second_string>)

( 5 * 200 ) / (10 + 5);
= 66.6666666667
12
spinsch

説明int similar_text(文字列$ first、文字列$ second [、float&$ percent])

これは、Oliver [1993]で説明されているように、2つの文字列間の類似性を計算します。この実装では、Oliverの擬似コードのようにスタックを使用しませんが、プロセス全体を高速化する場合としない場合がある再帰呼び出しを使用します。また、このアルゴリズムの複雑さはO(N ** 3)であり、Nは最も長い文字列の長さです。パラメーター

最初

The first string.

第二

The second string.

パーセント

By passing a reference as third argument, similar_text() will calculate the similarity in percent for you.
1
Kamesh S