web-dev-qa-db-fra.com

Comment fonctionne le texte similaire?

Je viens de trouver la fonction similar_text et je jouais avec, mais le pourcentage de sortie me surprend toujours. Voir les exemples ci-dessous.

J'ai essayé de trouver des informations sur l'algorithme utilisé comme mentionné sur 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

?>

Quelqu'un peut-il expliquer comment cela fonctionne réellement?

Mise à jour:

Grâce aux commentaires, j'ai trouvé que le pourcentage est réellement calculé en utilisant le nombre de caractères similaires * 200/longueur1 + longueur 2

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

Cela explique donc pourquoi les percenatges sont plus élevés que prévu. Avec une chaîne avec 5 sur 95, il s'avère 10, pour que je puisse l'utiliser.

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

Mais je n'arrive toujours pas à comprendre pourquoi PHP renvoie un résultat différent en retournant les chaînes. Le code JS fourni par dfsq ne fait pas cela. En regardant le code source dans PHP Je ne peux trouver une différence que dans la ligne suivante, mais je ne suis pas programmeur ac. Un aperçu de la différence serait apprécié.

En JS:

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

En PHP: (fonction php_similar_str)

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

La source:

/* {{{ 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;
}
/* }}} */

Source en Javascript: port de texte similaire à javascript

54
Hugo Delsing

Il semblerait en effet que la fonction utilise une logique différente en fonction de l'ordre des paramètres. Je pense qu'il y a deux choses en jeu.

Tout d'abord, voyez cet exemple:

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

Il semble qu'il teste "combien de fois un caractère distinct sur param1 est trouvé dans param2", et le résultat serait donc différent si vous échangez les paramètres. Il a été signalé comme bug , qui a été fermé comme "fonctionnant comme prévu".

Maintenant, ce qui précède est le même pour les deux implémentations PHP et javascript - l'ordre des paramètres a un impact, donc dire que Le code JS ne ferait pas cela est faux. Ceci est argumenté dans l'entrée de bogue comme comportement prévu.

Deuxièmement - ce qui ne semble pas correct est l'exemple de Word MYSQL/PHP. Avec cela, la version javascript donne 3 sans rapport avec l'ordre des paramètres, tandis que PHP donne 2 et 3 (et à cause de cela, le pourcentage est également différent). Maintenant, les phrases "PHP IS GREAT "et" WITH MYSQL "devraient avoir 5 caractères en commun, sans rapport avec la façon dont vous comparez: H, I, S et T, un chacun, plus un pour l'espace vide. Pour qu'ils aient 3 caractères, "H", "" et "S", donc si vous regardez l'ordre, la bonne réponse devrait être 3 dans les deux sens. J'ai modifié le code C en une version exécutable, et ajouté une sortie, afin que l'on puisse voir ce qui est qui se passe là-bas ( lien codepad ):

#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;
}

le résultat est sorti:

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

On peut donc voir que sur la première comparaison, la fonction a trouvé 'H', '' et 'S', mais pas 'T', et a obtenu le résultat de 3. La deuxième comparaison a trouvé 'I' et 'T' mais pas 'H', '' ou 'S', et a donc obtenu le résultat de 2.

La raison de ces résultats peut être vue à partir de la sortie: l'algorithme prend la première lettre de la première chaîne que la deuxième chaîne contient, compte cela et jette les caractères avant celui de la deuxième chaîne . C'est pourquoi il manque les personnages entre les deux, et c'est la chose qui fait la différence lorsque vous changez l'ordre des personnages.

Ce qui se passe peut être intentionnel ou non. Cependant, ce n'est pas ainsi que fonctionne la version javascript. Si vous imprimez les mêmes choses dans la version javascript, vous obtenez ceci:

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

montrant que la version javascript le fait d'une manière différente. Ce que fait la version javascript, c'est qu'elle trouve que "H", "" et "S" sont dans le même ordre dans la première comparaison, et le même "H", "" et "S" également dans la seconde - donc dans dans ce cas, l'ordre des paramètres n'a pas d'importance.

Comme le javascript est destiné à dupliquer le code de PHP, il doit se comporter de manière identique, j'ai donc soumis un rapport de bogue basé sur l'analyse de @Khez et du correctif, qui a été fusionné maintenant.

27
eis

C'était en fait une question très intéressante, merci de m'avoir donné un puzzle qui s'est avéré très enrichissant.

Permettez-moi de commencer par expliquer comment similar_text fonctionne réellement.


Texte similaire: l'algorithme

C'est une récursion basée sur algorithme de division et de conquête . Cela fonctionne en trouvant d'abord la plus longue chaîne commune entre les deux entrées et en divisant le problème en sous-ensembles autour de cette chaîne.

Les exemples que vous avez utilisés dans votre question exécutent tous une seule itération de l'algorithme . Les seuls qui n'utilisent pas une seule itération et ceux qui donnent des résultats différents sont des commentaires php.net .

Voici un exemple simple pour comprendre le problème principal derrière simple_text et, espérons-le, donner un aperçu de son fonctionnement.


Texte similaire: The Flaw

eeeefaaaaafddddd
ddddgaaaaagbeeee

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

J'espère que la faille est déjà apparente. Il vérifiera uniquement directement à gauche et à droite de la chaîne correspondante la plus longue dans les deux chaînes d'entrée. Cet exemple

$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

Pour être honnête, je ne sais pas comment ce cas devrait être traité. On peut voir que seuls 2 caractères sont différents dans la chaîne. Mais eeee et dddd se trouvent aux extrémités opposées des deux chaînes, Je ne sais pas ce que NLP passionnés ou autres littéraires experts ont à dire sur cette situation spécifique.


Texte similaire: Résultats incohérents lors de l'échange d'arguments

Les différents résultats que vous rencontriez en fonction de l'ordre d'entrée étaient dus à la façon dont l'alogirthm se comporte réellement (comme mentionné ci-dessus). Je vais donner une dernière explication sur ce qui se passe.

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

Dans le premier cas, il n'y a qu'une seule itération:

test
wert

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

Nous n'avons qu'une seule itération car les chaînes vides/nulles retournent 0 lors de la récursivité. Cela termine donc l'algorithme et nous avons notre résultat: 1

Dans le deuxième cas, cependant, nous sommes confrontés à plusieurs itérations:

wert
test

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

Nous avons déjà une chaîne commune de longueur 1. L'algorithme du sous-ensemble de gauche se terminera par 0 correspondances, mais à droite:

rt
st

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

Cela conduira à notre nouveau résultat final: 2

Je vous remercie pour cette question très informative et pour l'opportunité de tester à nouveau le C++.


Texte similaire: JavaScript Edition

La réponse courte est: Le code javascript n'implémente pas l'algorithme correct

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

Évidemment, ce devrait être first.substr(0,pos1)

Remarque: Le code JavaScript a été corrigé par eis dans un validation précédente . Merci @ eis

Démystifié!

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

Description int similar_text (chaîne $ first, chaîne $ second [ float & $ percent])

Ceci calcule la similitude entre deux chaînes comme décrit dans Oliver [1993]. Notez que cette implémentation n'utilise pas une pile comme dans le pseudo-code d'Oliver, mais des appels récursifs qui peuvent ou non accélérer le processus entier. Notez également que la complexité de cet algorithme est O (N ** 3) où N est la longueur de la chaîne la plus longue. Paramètres

premier

The first string.

seconde

The second string.

pour cent

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