Suchen Sie das erste Zeichen, das sich zwischen zwei Zeichenfolgen unterscheidet

71

Gibt es bei zwei gleich langen Zeichenfolgen eine elegante Möglichkeit, den Versatz des ersten unterschiedlichen Zeichens zu ermitteln?

Die offensichtliche Lösung wäre:

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

Aber das sieht für eine so einfache Aufgabe nicht ganz richtig aus.

NikiC
quelle
2
Verwandte: Tetris'ing ein Array
Pekka
8
Sieht für mich einfach aus.
Leichtigkeitsrennen im Orbit
Es gibt effizientere Möglichkeiten, dies zu tun, aber möglicherweise komplizierter zu lesen. Wird dieses Codebit oft aufgerufen? Dh Ist es wichtig, ob es effizient ist?
Robert Martin
2
@ Robert: Wie könnte es effizienter gemacht werden? Dies ist O(n)und Sie werden untersuchen , bis müssen nZeichen.
Leichtigkeitsrennen im Orbit
4
! BE AWARE!, Dass dies beim Umgang mit Unicode-Zeichen zu einem falschen Offset führen kann. Wenn Sie es so machen möchten, verwenden Sie besser mb_substr ()
breiti

Antworten:

176

Sie können eine nette Eigenschaft von bitweisem XOR ( ^) verwenden , um dies zu erreichen: Wenn Sie zwei Zeichenfolgen zusammen xorieren, werden die gleichen Zeichen zu Null-Bytes ( "\0"). Wenn wir also die beiden Zeichenfolgen xorieren, müssen wir nur die Position des ersten Nicht-Null-Bytes ermitteln, indem wir strspn:

$position = strspn($string1 ^ $string2, "\0");

Das ist alles dazu. Schauen wir uns also ein Beispiel an:

$string1 = 'foobarbaz';
$string2 = 'foobarbiz';
$pos = strspn($string1 ^ $string2, "\0");

printf(
    'First difference at position %d: "%s" vs "%s"',
    $pos, $string1[$pos], $string2[$pos]
);

Das wird ausgegeben:

Erster Unterschied an Position 7: "a" gegen "i"

Das sollte es also tun. Es ist sehr effizient, da nur C-Funktionen verwendet werden und nur eine einzige Kopie des Speichers der Zeichenfolge erforderlich ist.

Bearbeiten: Eine MultiByte-Lösung entlang derselben Linie:

function getCharacterOffsetOfDifference($str1, $str2, $encoding = 'UTF-8') {
    return mb_strlen(
        mb_strcut(
            $str1,
            0, strspn($str1 ^ $str2, "\0"),
            $encoding
        ),
        $encoding
    );
}

Zuerst wird der Unterschied auf Byte-Ebene mit der obigen Methode ermittelt und dann der Offset auf die Zeichenebene abgebildet. Dies geschieht mit der mb_strcutFunktion, die im Grunde genommen substrjedoch die Grenzen von Multibyte-Zeichen berücksichtigt.

var_dump(getCharacterOffsetOfDifference('foo', 'foa')); // 2
var_dump(getCharacterOffsetOfDifference('©oo', 'foa')); // 0
var_dump(getCharacterOffsetOfDifference('f©o', 'fªa')); // 1

Es ist nicht so elegant wie die erste Lösung, aber es ist immer noch ein Einzeiler (und wenn Sie die Standardcodierung etwas einfacher verwenden):

return mb_strlen(mb_strcut($str1, 0, strspn($str1 ^ $str2, "\0")));
ircmaxell
quelle
10
Bist du ein Wecker? Woher wusste NikiC, dass Sie dies veröffentlichen möchten ?
Robert Martin
12
@ Robert Martin, besuchen Sie unsere Kurse von Telepathie hier .
OZ_
5
@ Robert: Ja, das bin ich. Wir hatten dies gestern besprochen und Nikic hatte mich gebeten, diese Lösung jetzt hier zu veröffentlichen, um eine Basis zu geben, um zu sehen, ob es andere (möglicherweise bessere) Lösungen als diese gibt. Und um auch andere Kommentare dazu zu bekommen ...
ircmaxell
2
Warum aus Neugier das Downvote? Gibt es etwas, das verbessert oder erweitert werden kann (und als solches vielleicht diskutiert werden sollte)?
Ircmaxell
1
Ich denke, es hängt mit dem Unterschied in den Upvotes zu Kommentar Nr. 1 und Kommentar Nr. 2 zusammen (leider).
JK.
16

Wenn Sie eine Zeichenfolge in ein Array mit Einzelbyte-Ein-Byte-Werten konvertieren, können Sie die Zeichenfolgen mithilfe der Array-Vergleichsfunktionen vergleichen.

Sie können mit der folgenden Methode ein ähnliches Ergebnis wie mit der XOR-Methode erzielen.

$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]'.";

Bearbeiten: Multibyte-Lösung

$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";
Steve Buzonas
quelle
Ich bin nicht sehr vertraut mit der Arbeit mit Multibyte-Codierung. Wenn jemand mehr Einblick geben könnte, wie dies funktionieren würde / wie str_split mit mb ​​funktioniert, wäre er sehr dankbar.
Steve Buzonas
1
Es funktioniert nicht mit Multibyte-Codierungen. Wenn Sie das wollten, $array = preg_split('((.))u', $string, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
müssten
Danke für den preg_splitTipp, fügte ihn der Antwort hinzu.
Steve Buzonas
4

Ich wollte dies als Kommentar zur besten Antwort hinzufügen, aber ich habe nicht genug Punkte.

$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';
}
Bradley Slavik
quelle
-5
string strpbrk ( string $haystack , string $char_list )

strpbrk () durchsucht den Heuhaufen-String nach einer char_list.

Der Rückgabewert ist die Teilzeichenfolge von $ haystack, die beim ersten übereinstimmenden Zeichen beginnt. Als API-Funktion sollte es flink sein. Durchlaufen Sie dann einmal und suchen Sie nach dem Offset Null der zurückgegebenen Zeichenfolge, um Ihren Offset zu erhalten.

Sinthia V.
quelle
Was ist, wenn eine Saite "foobarr" mit einer Saite "foobaar" verglichen wird? Es gibt keinen Unterschied im Zeichensatz, nur die Anzahl und Positionierung.
Steve Buzonas
Hier nicht anwendbar. Wenn beispielsweise Heuhaufen abcdefund char_list ist fedcba, wird die gesamte Zeichenfolge zurückgegeben (da sie ain der char-Liste enthalten ist). Während diese Funktion für eine sehr begrenzte Teilmenge möglicher Eingaben funktioniert, funktioniert sie nicht generisch, sodass sie keine gute Antwort auf die Frage ist.
Ircmaxell
@NikiC fragte nach "einem eleganten Weg, um den Versatz des ersten anderen Zeichens zu erhalten". Das erste Zeichen in Ihrem Beispiel ist die richtige Antwort, ircmaxell. Während Steve einen besseren Punkt hat. Ich liebe den xor-Ansatz, aber Unicode ist die Fliege in dieser Salbe. Hmmmm ....
Sinthia V
@Sinthia: Richtig, aber es würde auch zurückkehren, abcdefwenn die char_list ebenfalls ist abcdef. Es ist also nur "zufällig", dass die richtige Antwort zurückgegeben wird.
Ircmaxell