【问题标题】:Find first character that is different between two strings查找两个字符串之间不同的第一个字符
【发布时间】:2011-11-20 11:38:57
【问题描述】:

给定两个长度相等的字符串,有没有一种优雅的方法来获取第一个不同字符的偏移量?

显而易见的解决方案是:

for ($offset = 0; $offset < $length; ++$offset) {
    if ($str1[$offset] !== $str2[$offset]) {
        return $offset;
    }
}

但是对于这么简单的任务,这看起来不太正确。

【问题讨论】:

  • 对我来说看起来很简单。
  • 有更有效的方法可以做到这一点,但阅读起来可能更复杂。这段代码会被调用很多次吗? IE。有没有效率重要吗?
  • @Robert:如何才能更有效地完成?这是O(n),您必须检查最多n 个字符。
  • !注意!在处理 unicode 字符时,这可能会导致错误的偏移量。如果你想这样做,最好使用mb_substr()

标签: php string


【解决方案1】:

您可以使用bitwise XOR (^) 的一个很好的属性来实现此目的:基本上,当您将两个字符串异或时,相同的字符将变为空字节("\0")。所以如果我们对两个字符串进行异或,我们只需要使用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")));

【讨论】:

  • 你是敲门人吗? NikiC 是如何知道您打算发布此内容的?
  • @Robert Martin,访问我们的心灵感应课程here
  • @Robert:是的,我是。我们昨天讨论过这个问题,Nikic 让我现在在这里发布这个解决方案,以提供一个基线,看看是否有任何其他(可能更好)的解决方案比这个解决方案。并让其他人的 cmets 也参与其中......
  • 出于好奇,为什么要投反对票?有什么可以改进或扩展的(因此也许应该讨论)?
  • 我猜这与评论 #1 和评论 #2 的赞成票差异有关(不幸的是)。
【解决方案2】:

如果将字符串转换为单字符单字节值的数组,则可以使用数组比较函数来比较字符串。

您可以使用以下方法获得与 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";

【讨论】:

  • 我不太熟悉使用多字节编码。如果有人能更深入地了解这将如何保持/str_split 如何与 mb 一起工作,将不胜感激。
  • 它不适用于多字节编码。如果你想要这样,你几乎必须使用这样的东西:$array = preg_split('((.))u', $string, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); 基本上,它会分成单独的 UTF-8 字符......
  • 感谢preg_split 提示,已将其添加到答案中。
【解决方案3】:

我想将此作为评论添加到最佳答案,但我没有足够的积分。

$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';
}

【讨论】:

    【解决方案4】:
    string strpbrk ( string $haystack , string $char_list )
    

    strpbrk() 在 haystack 字符串中搜索 char_list。

    返回值是 $haystack 从第一个匹配字符开始的子字符串。 作为一个 API 函数,它应该是 zippy 的。然后循环一次,寻找返回字符串的零偏移量来获得你的偏移量。

    【讨论】:

    • 当您将字符串“foobarr”与字符串“foobaar”进行比较时会怎样。字符集没有区别,只是计数和定位。
    • 此处不适用。例如,如果 haystack 是 abcdef 并且 char_list 是 fedcba 它将返回整个字符串(因为 a 在 char 列表中)。因此,虽然此函数适用于可能输入的非常有限的子集,但它不能以通用方式工作,因此它不是问题的好答案。
    • @NikiC 要求“一种优雅的方式来获取第一个不同字符的偏移量”。您示例中的第一个字符是正确答案,ircmaxell。虽然史蒂夫有更好的观点。我喜欢 xor 方法,但 unicode 是美中不足的。嗯……
    • @Sinthia:正确,但是当 char_list 也是 abcdef 时,它也会返回 abcdef。所以它返回正确答案只是“偶然”。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-03
    • 2014-12-07
    • 1970-01-01
    • 2023-03-10
    • 1970-01-01
    • 2017-04-07
    • 1970-01-01
    相关资源
    最近更新 更多