Поиск совпадающих частей двух строк в PHP

Я ищу простой способ найти соответствующие части двух строк в PHP (в частности, в контексте URI)

например, рассмотрим две строки:

http://2.2.2.2 / ~machinehost / deployment_folder/

и

/ ~machinehost / deployment_folder/users/bob / settings

Что мне нужно, это отрубить соответствующую часть этих двух строк из второй строки, в результате чего в:

пользователи/Вася/настройки

перед добавлением первой строки в качестве префикса, образуя абсолютный URI.

есть ли простой способ (в PHP) сравнить две произвольные строки для сопоставления подстрок внутри них?

EDIT: как указано, я имел в виду самая длинная соответствующая подстрока, общая для обеих строк

6 ответов


этой будет ответ. Готовая к использованию функция PHP.


предполагая, что ваши строки $a и $b, соответственно, вы можете использовать это:

$a = 'http://2.2.2.2/~machinehost/deployment_folder/';
$b = '/~machinehost/deployment_folder/users/bob/settings';

$len_a = strlen($a);
$len_b = strlen($b);

for ($p = max(0, $len_a - $len_b); $p < $len_b; $p++)
    if (substr($a, $len_a - ($len_b - $p)) == substr($b, 0, $len_b - $p))
        break;

$result = $a.substr($b, $len_b - $p);

echo $result;

этот результат http://2.2.2.2/~machinehost/deployment_folder/users/bob/settings.


Я не уверен, что понимаю ваш полный запрос, но идея:

пусть A-Ваш URL и B-ваш "/ ~machinehost/deployment_folder / users/bob / settings"

  • поиск B В A - > вы получаете индекс i (где i-позиция первого / из B В A)
  • пусть l = длина (A)
  • вам нужно вырезать B из (l-i) до длины (B), чтобы захватить последнюю часть B (/users/bob/settings)

Я еще не проверял, но если вам действительно нужно, Я могу помочь вам сделать это блестящее (ироничное) решение работать.

обратите внимание, что это может быть возможно с регулярными выражениями, такими как

$pattern = "$B(.*?)"
$res = array();
preg_match_all($pattern, $A, $res);

Edit: я думаю, что ваш последний комментарий аннулирует мой ответ. Но вы хотите найти подстроки. Таким образом, вы можете начать с тяжелого алгоритма, пытающегося найти B[1:i] в A для i в {2, length(B)}, а затем использовать некоторые динамическое программирование питания.


Это, похоже, не из кода коробки там для вашего требования. Поэтому давайте искать простой способ.

на FindLongestMatch () метод, разбирает путь, кусок за куском ищет совпадение в другом пути, сохраняя только одно совпадение, самое длинное (без массивов, без сортировки). Этот RemoveLongestMatch () метод принимает суффикс или "остаток" после самого длинного совпадения найденной позиции.

вот полный исходный код:

<?php

function FindLongestMatch($relativePath, $absolutePath)
{
    static $_separator = '/';
    $splitted = array_reverse(explode($_separator, $absolutePath));

    foreach ($splitted as &$value)
    {
        $matchTest = $value.$_separator.$match;
        if(IsSubstring($relativePath, $matchTest))
            $match = $matchTest;

        if (!empty($value) && IsNewMatchLonger($match, $longestMatch))
            $longestMatch = $match;
    }

    return $longestMatch;
}

//Removes from the first string the longest match.
function RemoveLongestMatch($relativePath, $absolutePath)
{
    $match = findLongestMatch($relativePath, $absolutePath);
    $positionFound = strpos($relativePath, $match);     
    $suffix = substr($relativePath, $positionFound + strlen($match));

    return $suffix;
}

function IsNewMatchLonger($match, $longestMatch)
{
    return strlen($match) > strlen($longestMatch);
}

function IsSubstring($string, $subString)
{
    return strpos($string, $subString) > 0;
}

это репрезентативное подмножество тестовых случаев:

//TEST CASES
echo "<br>-----------------------------------------------------------"; 
echo "<br>".$absolutePath = 'http://2.2.2.2/~machinehost/deployment_folder/';
echo "<br>".$relativePath = '/~machinehost/deployment_folder/users/bob/settings';
echo "<br>Longest match: ".findLongestMatch($relativePath, $absolutePath);
echo "<br>Suffix: ".removeLongestMatch($relativePath, $absolutePath);

echo "<br>-----------------------------------------------------------"; 
echo "<br>".$absolutePath = 'http://1.1.1.1/root/~machinehost/deployment_folder/';
echo "<br>".$relativePath = '/root/~machinehost/deployment_folder/users/bob/settings';
echo "<br>Longest match: ".findLongestMatch($relativePath, $absolutePath);
echo "<br>Suffix: ".removeLongestMatch($relativePath, $absolutePath);

echo "<br>-----------------------------------------------------------"; 
echo "<br>".$absolutePath = 'http://2.2.2.2/~machinehost/deployment_folder/users/';
echo "<br>".$relativePath = '/~machinehost/deployment_folder/users/bob/settings';
echo "<br>Longest match: ".findLongestMatch($relativePath, $absolutePath);
echo "<br>Suffix: ".removeLongestMatch($relativePath, $absolutePath);

echo "<br>-----------------------------------------------------------"; 
echo "<br>".$absolutePath = 'http://3.3.3.3/~machinehost/~machinehost/subDirectory/deployment_folder/';
echo "<br>".$relativePath = '/~machinehost/subDirectory/deployment_folderX/users/bob/settings';
echo "<br>Longest match: ".findLongestMatch($relativePath, $absolutePath);
echo "<br>Suffix: ".removeLongestMatch($relativePath, $absolutePath);

запуск предыдущих тестовых случаев обеспечивает следующий вывод:

http://2.2.2.2/~machinehost/deployment_folder/
/~machinehost/deployment_folder/users/bob/settings
Longuest match: ~machinehost/deployment_folder/
Suffix: users/bob/settings

http://1.1.1.1/root/~machinehost/deployment_folder/
/root/~machinehost/deployment_folder/users/bob/settings
Longuest match: root/~machinehost/deployment_folder/
Suffix: users/bob/settings

http://2.2.2.2/~machinehost/deployment_folder/users/
/~machinehost/deployment_folder/users/bob/settings
Longuest match: ~machinehost/deployment_folder/users/
Suffix: bob/settings

http://3.3.3.3/~machinehost/~machinehost/subDirectory/deployment_folder/
/~machinehost/subDirectory/deployment_folderX/users/bob/settings
Longuest match: ~machinehost/subDirectory/
Suffix: deployment_folderX/users/bob/settings

возможно, вы можете взять идею этого фрагмента кода и превратить его в то, что вы найдете полезным для вашего текущего проекта. Дайте мне знать, если это сработает и для тебя тоже. Кстати, Мистер Орекс тоже неплохо выглядит.


поиск самого длинного общего совпадения также можно сделать с помощью regex.

функция ниже будет принимать две строки, использовать одну для создания регулярного выражения и выполнять его против другой.

/**
 * Determine the longest common match within two strings
 *
 * @param string $str1
 * @param string $str2 Two strings in any order.
 * @param boolean $case_sensitive Set to true to force
 * case sensitivity. Default: false (case insensitive).
 * @return string The longest string - first match.
 */
function get_longest_common_subsequence( $str1, $str2, $case_sensitive = false ) {
    // We'll use '#' as our regex delimiter. Any character can be used as we'll quote the string anyway,
    $delimiter = '#';

    // We'll find the shortest string and use that to create our regex.
    $l1 = strlen( $str1 );
    $l2 = strlen( $str2 );
    $str = $l1 <= $l2 ? $str1 : $str2;
    $l = min( $l1, $l2 );

    // Regex for each character will be of the format (?:a(?=b))?
    // We also need to capture the last character, but this prevents us from matching strings with a single character. (?:.|c)?
    $reg = $delimiter;
    for ( $i = 0; $i < $l; $i++ ) {
        $a = preg_quote( $str[ $i ], $delimiter );
        $b = $i + 1 < $l ? preg_quote( $str[ $i + 1 ], $delimiter ) : false;
        $reg .= sprintf( $b !== false ? '(?:%s(?=%s))?' : '(?:.|%s)?', $a, $b );
    }
    $reg .= $delimiter;
    if ( ! $case_sensitive ) {
        $reg .= 'i';
    }
    // Resulting example regex from a string 'abbc':
    // '#(?:a(?=b))?(?:b(?=b))?(?:b(?=c))?(?:.|c)?#i';

    // Perform our regex on the remaining string
    $str = $l1 <= $l2 ? $str2 : $str1;
    if ( preg_match_all( $reg, $str, $matches ) ) {
        // $matches is an array with a single array with all the matches.
        return array_reduce( $matches[0], function( $a, $b ) {
            $al = strlen( $a );
            $bl = strlen( $b );
            // Return the longest string, as long as it's not a single character.
            return $al >= $bl || $bl <= 1 ? $a : $b;
        }, '' );
    }

    // No match - Return an empty string.
    return '';
}

Он будет генерировать регулярное выражение с помощью наименьшего из двух строк, хотя производительность скорее всего будет одинаково. Он может неправильно соответствовать строкам с повторяющимися подстроками, и мы ограничены совпадающими строками из двух символов или более. Для Пример:

// Works as intended.
get_longest_common_subsequence( 'abbc', 'abc' ) === 'ab';

// Returns incorrect substring based on string length and recurring substrings.
get_longest_common_subsequence( 'abbc', 'abcdef' ) === 'abc';

// Does not return any matches.
get_longest_common_subsequence( 'abc', 'ace' ) === '';

независимо, он функционирует с использованием альтернативного метода, и регулярное выражение может быть уточнено для решения дополнительных ситуаций.


попробуйте это.

http://pastebin.com/GqS3UiPD