Как работает подобный текст?
Я только что нашел функцию 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 + lenght 2
Z_DVAL_PP(percent) = sim * 200.0 / (t1_len + t2_len);
таким образом, это объясняет, почему проценты выше, чем ожидалось. Со строкой с 5 из 95 получается 10, так что я могу использовать.
similar_text('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'aaaaa', $p);
echo $p . "<hr>";
//10
//5 out of 95 = 5 * 200 / (5 + 95) = 10
но я все еще не могу понять, почему PHP возвращает другой результат при повороте строк. Код JS, предоставленный dfsq, этого не делает. Глядя на исходный код в PHP я могу найти только разницу в следующей строке, но я не программист. Понять в чем разница, буду признателен.
в 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 ответа:
казалось бы, функция использует разную логику в зависимости от порядка параметров. Я думаю, что есть две вещи в игре.
во-первых, смотрите этот пример:
echo similar_text('test','wert'); // 1 echo similar_text('wert','test'); // 2
похоже, что он проверяет "сколько раз любой отдельный символ на param1 находится в param2", и, таким образом, результат будет отличаться, если вы поменяете параметры вокруг. Это было сообщено как ошибка, который не был подтвержден никем.
теперь, выше то же самое для реализаций PHP и javascript - порядок paremeter оказывает влияние, поэтому говорить, что JS-код не будет этого делать, неправильно. Я думаю, что это возможно чтобы утверждать, что это как предполагаемое поведение. Не уверен, что это так.
во - вторых-то, что не кажется правильным, является примером слова MYSQL/PHP. При этом версия javascript дает 3 нерелевантных порядка параметров, тогда как PHP дает 2 и 3 (и из-за этого процент одинаково отличается). Теперь, фразы "PHP отлично" и "с MYSQL" должны иметь 5 общих символов, не имеющих отношения к тому, как вы сравниваете: H, I, S и T, по одному каждому, плюс один для пустого пространства. Для того, чтобы они имели 3 символа, 'H', '' и 'S', так что если вы посмотрите на порядок, правильный ответ должен быть 3 в обе стороны. Я изменил код 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. Второе сравнение обнаружило "I" и "T", но не "H", "' или "S", и таким образом получило результат 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 " в том же порядке в первом сравнении, и то же самое "H", " 'и" S " также на втором - так что в этом случае порядок параметров не имеет значения.
Я бы сказал, что версия javascript является более правильным способом сделать это, но это для спекуляций. В любом случае, поскольку javascript предназначен для дублирования кода функции PHP, он должен вести себя одинаково - именно поэтому я представил отчет об ошибке на основе анализа @Khez и исправления. Слава там.
это был на самом деле очень интересный вопрос, Спасибо за то, что дал мне головоломку, которая оказалась очень полезной.
позвольте мне начать с объяснения того, как similar_text на самом деле работает.
Похожий Текст: Алгоритм
это рекурсия на основе разделяй и властвуй. Он работает, сначала найдя самая длинная общая строка между двумя входами и ломать проблему в подмножества вокруг этой строки.
примеры, которые вы использовали в вашем вопросе, на самом деле все выполняют только одна итерация алгоритма. Единственные, кто не использует одну итерацию, и те, которые дают разные результаты, - это 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 символа в строку. Но оба ээээ и dddd находятся на противоположных концах двух строк, неизвестно, что НЛП энтузиастов или другие литературные эксперты должны сказать об этой конкретной ситуации.
аналогичный текст: несогласованные результаты при замене аргументов
различные результаты, которые вы испытывали на основе порядка ввода, были связаны с тем, как alogirthm фактически ведет себя (как упоминалось выше). Я дам последнее объяснение тому, что происходит.
echo similar_text('test','wert'); // 1 echo similar_text('wert','test'); // 2
в первом случае, есть только один Итерация:
test wert Iteration 1: Max = 1 String = t Left : and wer Right: est and
у нас есть только одна итерация, потому что пустые/нулевые строки возвращают 0 при рекурсии. Так что на этом алгоритм заканчивается и мы получаем наш результат: 1
во втором случае, однако, мы сталкиваемся с несколькими итерациями:
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
спасибо вам за этот очень информативный вопрос и возможность снова баловаться с++.
Подобный Текст: JavaScript Edition
короткий ответ: код javascript не реализует правильный алгоритм
sum += this.similar_text(first.substr(0, pos2), second.substr(0, pos2));
очевидно, что это должно быть
first.substr(0,pos1)
Примечание: код JavaScript был исправлен с помощью eis на предыдущий. Спасибо @eis
Демистифицированный!
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
описание тип int similar_text ( строка $во-первых , строки в секунду [, поплавок &$процентов ] )
это вычисляет сходство между двумя строками, как описано в Oliver [1993]. Обратите внимание, что эта реализация не использует стек, как в псевдокоде Оливера, но рекурсивные вызовы, которые могут или не могут ускорить весь процесс. Отметим также, что сложность этого алгоритма равна 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.