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.


2

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")));

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";

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

-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.


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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.