54

similar_text 関数を見つけて、いじっていましたが、パーセンテージの出力にはいつも驚かされます。以下の例を参照してください。

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

<?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 + length 2 を使用して計算されることがわかりました

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

これで、パーセンテージが予想よりも高い理由が説明できます。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 と同様のテキスト ポート

4

4 に答える 4

30

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

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 

空/null 文字列は再帰で 0 を返すため、反復は 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 版

簡単な答えは次のとおりです。JavaScriptコードは正しいアルゴリズムを実装していません

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

明らかにそうあるべきだfirst.substr(0,pos1)

注: JavaScript コードは、以前の commitでeisによって修正されています。ありがとう@eis

謎解き!

于 2013-01-09T01:06:09.193 に答える
27

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

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

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

「param1の個別の文字がparam2で何回見つかったか」をテストしているように見えるため、paramsを交換すると結果が異なります。これはバグとして報告されており、「期待どおりに動作する」としてクローズされました。

上記は、 PHP と JavaScript の両方の実装で同じです。パラメーターの順序が影響するため、JS コードがこれを行わないというのは間違っています。これは、意図した動作としてバグ エントリで主張されています。

2 つ目 - 正しくないように見えるのは、MYSQL/PHP の単語の例です。これにより、javascript バージョンは params の順序とは無関係に 3 を返しますが、PHP は 2 と 3 を返します (そのため、パーセンテージは等しく異なります)。ここで、"PHP IS GREAT" と "WITH MYSQL" というフレーズには、H、I、S、T の各 1 文字と、空白を表す 1 文字の 5 つの共通文字が必要です。順番は「H」「S」「S」の3文字なので、順番を見ると正解は「3」と「S」のはずです。Cコードを実行可能なバージョンに変更し、いくつかの出力を追加したので、そこで何が起こっているかを見ることができます( codepad link ):

#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の分析と修正に基づいてバグレポートを提出し、現在マージされています。

于 2013-01-04T13:32:00.473 に答える
12
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
于 2013-01-03T10:39:50.857 に答える
1

説明 int similar_text ( string $first , string $second [, float &$percent ] )

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

最初

The first string.

2番目

The second string.

パーセント

By passing a reference as third argument, similar_text() will calculate the similarity in percent for you.
于 2013-01-14T04:50:01.863 に答える