Учитывая две строки равной длины, есть ли элегантный способ получить смещение первого другого персонажа?
Очевидным решением будет:
for ($offset = 0; $offset < $length; ++$offset) { if ($str1[$offset] !== $str2[$offset]) { return $offset; } }
Но для такой простой задачи это выглядит не совсем правильно.
Вы можете использовать свойство nice побитового XOR ( ^
) для достижения этого: в основном, когда вы xor две строки вместе, одинаковые символы становятся нулевыми байтами ( "\0"
). Итак, если мы xor две строки, нам просто нужно найти позицию первого непустого байта, используя strspn
:
$position = strspn($string1 ^ $string2, "\0");
Вот и все. Итак, давайте посмотрим на пример:
$string1 = 'foobarbaz'; $string2 = 'foobarbiz'; $pos = strspn($string1 ^ $string2, "\0"); printf( 'First difference at position %d: "%s" vs "%s"', $pos, $string1[$pos], $string2[$pos] );
Это будет выводить:
Первое различие в позиции 7: «a» против «i»
Так что это нужно делать. Это очень эффективно, поскольку он использует только функции C и требует только одну копию памяти строки.
function getCharacterOffsetOfDifference($str1, $str2, $encoding = 'UTF-8') { return mb_strlen( mb_strcut( $str1, 0, strspn($str1 ^ $str2, "\0"), $encoding ), $encoding ); }
Сначала разность на уровне байта найдена с использованием вышеуказанного метода, а затем смещение отображается на уровень символов. Это делается с помощью функции mb_strcut
, которая в основном является substr
но соблюдает границы многобайтовых символов.
var_dump(getCharacterOffsetOfDifference('foo', 'foa')); // 2 var_dump(getCharacterOffsetOfDifference('©oo', 'foa')); // 0 var_dump(getCharacterOffsetOfDifference('f©o', 'fªa')); // 1
Это не так элегантно, как первое решение, но оно по-прежнему однострочное (и если вы используете кодировку по умолчанию немного проще):
return mb_strlen(mb_strcut($str1, 0, strspn($str1 ^ $str2, "\0")));
Если вы преобразуете строку в массив значений одного символа в один байт, вы можете использовать функции сравнения массива для сравнения строк.
Вы можете добиться аналогичного результата с помощью метода XOR со следующим.
$string1 = 'foobarbaz'; $string2 = 'foobarbiz'; $array1 = str_split($string1); $array2 = str_split($string2); $result = array_diff_assoc($array1, $array2); $num_diff = count($result); $first_diff = key($result); echo "There are " . $num_diff . " differences between the two strings. <br />"; echo "The first difference between the strings is at position " . $first_diff . ". (Zero Index) '$string1[$first_diff]' vs '$string2[$first_diff]'.";
$string1 = 'foorbarbaz'; $string2 = 'foobarbiz'; $array1 = preg_split('((.))u', $string1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); $array2 = preg_split('((.))u', $string2, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); $result = array_diff_assoc($array1, $array2); $num_diff = count($result); $first_diff = key($result); echo "There are " . $num_diff . " differences between the two strings.\n"; echo "The first difference between the strings is at position " . $first_diff . ". (Zero Index) '$string1[$first_diff]' vs '$string2[$first_diff]'.\n";
Я хотел добавить это как комментарий к лучшему ответу, но у меня недостаточно очков.
$string1 = 'foobarbaz'; $string2 = 'foobarbiz'; $pos = strspn($string1 ^ $string2, "\0"); if ($pos < min(strlen($string1), strlen($string2)){ printf( 'First difference at position %d: "%s" vs "%s"', $pos, $string1[$pos], $string2[$pos] ); } else if ($pos < strlen($string1)) { print 'String1 continues with' . substr($string1, $pos); } else if ($pos < strlen($string2)) { print 'String2 continues with' . substr($string2, $pos); } else { print 'String1 and String2 are equal'; }
string strpbrk ( string $haystack , string $char_list )
strpbrk () ищет строку haystack для char_list.
Возвращаемое значение представляет собой подстроку $ haystack, которая начинается с первого совпадающего символа. Как функция API, она должна быть незаметной. Затем прокрутите один раз, ища нулевое значение смещения возвращаемой строки, чтобы получить смещение.