Wie überprüfe ich, ob eine Zeichenfolge mit einer anderen Zeichenfolge in C beginnt?

82

Gibt es so etwas wie startsWith(str_a, str_b)in der Standard-C-Bibliothek?

Es sollte Zeiger auf zwei Zeichenfolgen enthalten, die mit null Bytes enden, und mir sagen, ob die erste auch am Anfang der zweiten vollständig angezeigt wird.

Beispiele:

"abc", "abcdef" -> true
"abcdef", "abc" -> false
"abd", "abdcef" -> true
"abc", "abc"    -> true
thejh
quelle
3
Ich denke, Ihr drittes Beispiel sollte ein echtes Ergebnis haben.
Michael Burr
Mögliches Duplikat von stackoverflow.com/questions/15515088/…
Vacing

Antworten:

72

Anscheinend gibt es dafür keine Standard-C-Funktion. Damit:

bool startsWith(const char *pre, const char *str)
{
    size_t lenpre = strlen(pre),
           lenstr = strlen(str);
    return lenstr < lenpre ? false : memcmp(pre, str, lenpre) == 0;
}

Beachten Sie, dass das oben Genannte schön und klar ist. Wenn Sie es jedoch in einer engen Schleife ausführen oder mit sehr großen Saiten arbeiten, bietet es nicht die beste Leistung, da es die gesamte Länge beider Saiten vorne abtastet ( strlen). Lösungen wie wj32 oder Christoph bieten möglicherweise eine bessere Leistung (obwohl dieser Kommentar zur Vektorisierung jenseits meines Wissens von C liegt). Beachten Sie auch , Fred Foo-Lösung , die vermeidet strlenauf str(er ist richtig, es ist nicht notwendig , wenn Sie verwenden strncmpstatt memcmp). Nur wichtig für (sehr) große Saiten oder wiederholte Verwendung in engen Schleifen, aber wenn es darauf ankommt, ist es wichtig.

T.J. Crowder
quelle
5
Ich sollte erwähnen, dass das Übliche darin besteht, dass der String der erste Parameter und das Präfix der zweite ist. Aber ich habe sie wie oben beibehalten, weil es so schien, als ob Ihre Frage umrahmt wurde ... Die Reihenfolge liegt ganz bei Ihnen, aber ich hätte es wirklich anders herum machen sollen - die meisten String-Funktionen nehmen den vollen String als erstes Argument, der Teilstring als zweiter.
TJ Crowder
1
Dies ist eine elegante Lösung, die jedoch einige Leistungsprobleme aufweist. Eine optimierte Implementierung würde niemals mehr als min (strlen (pre), strlen (str)) Zeichen aus jeder Zeichenkette betrachten, noch würde sie jemals über die erste Nichtübereinstimmung hinausschauen. Wenn die Saiten lang wären, aber frühe Fehlpaarungen häufig wären, wäre sie sehr leicht. Da diese Implementierung jedoch die gesamte Länge beider Zeichenfolgen von vornherein beansprucht, wird die Leistung im ungünstigsten Fall erzwungen, selbst wenn sich die Zeichenfolgen im ersten Zeichen unterscheiden. Ob dies wirklich wichtig ist, hängt von den Umständen ab, aber es ist ein potenzielles Problem.
Tom Karzes
1
@TomKarzes Sie ersetzen können memcmpfür strncmphier und es ist schneller. Es gibt keine UB, da bekannt ist, dass beide Zeichenfolgen mindestens lenpreBytes haben. strncmpÜberprüft jedes Byte beider Zeichenfolgen auf NUL, aber die strlenAufrufe haben bereits garantiert, dass keine vorhanden sind. (Aber es hat immer noch den Performance-Hit, den Sie erwähnt haben, wenn preoder strlänger als die eigentliche gemeinsame Anfangssequenz.)
Jim Balter
1
@ JimBalter - Sehr guter Punkt! Da die Verwendung der memcmpobigen Informationen hier nicht für eine andere Antwort geeignet wäre, habe ich sie in der Antwort geändert.
TJ Crowder
1
PS Dieser (jetzt) mit einigen Streichern die schnellste Antwort auf einige Maschinen, da strlenund memcmpkann mit sehr schnellen Hardware-Anweisungen umgesetzt werden, und die strlens die Saiten in die Cache stellen kann, einen doppelten Speichertreffer zu vermeiden. Auf solchen Maschinen strncmpkönnte dies als zwei strlenSekunden und memcmpgenau so implementiert werden , aber es wäre für einen Bibliotheksschreiber riskant, dies zu tun, da dies bei langen Zeichenfolgen mit kurzen gemeinsamen Präfixen viel länger dauern könnte. Hier ist dieser Treffer explizit und die strlens werden jeweils nur einmal ausgeführt (Fred Foo's strlen+ strncmpwürde 3 ausführen ).
Jim Balter
155

Es gibt keine Standardfunktion dafür, aber Sie können definieren

bool prefix(const char *pre, const char *str)
{
    return strncmp(pre, str, strlen(pre)) == 0;
}

Wir müssen uns keine Sorgen machen, strdass wir kürzer sind als prenach dem C-Standard (7.21.4.4/2):

Die strncmpFunktion vergleicht nicht mehr als nZeichen (Zeichen, die auf ein Nullzeichen folgen, werden nicht verglichen) von dem Array, auf das gezeigt wird, s1mit dem Array, auf das gezeigt wird s2. "

Fred Foo
quelle
12
Warum lautet die Antwort nein? Die Antwort lautet eindeutig Ja, sie heißt strncmp.
Jasper
6
^ Es sollte offensichtlich sein, warum die Antwort nein ist. Ein Algorithmus, der strncmp verwendet strncmpund strlennicht "strncmp" heißt.
Jim Balter
33

Ich würde wahrscheinlich mitmachen strncmp(), aber nur zum Spaß eine rohe Implementierung:

_Bool starts_with(const char *restrict string, const char *restrict prefix)
{
    while(*prefix)
    {
        if(*prefix++ != *string++)
            return 0;
    }

    return 1;
}
Christoph
quelle
6
Das gefällt mir am besten - es gibt keinen Grund, eine der Zeichenfolgen nach einer Länge zu durchsuchen.
Michael Burr
1
Ich würde wahrscheinlich auch mit strlen + strncmp gehen, aber obwohl es tatsächlich funktioniert, schreckt mich die Kontroverse über die vage Definition ab. Also werde ich das benutzen, danke.
Sam Watkins
4
Dies ist wahrscheinlich langsamer als strncmp, es sei denn, Ihr Compiler ist wirklich gut in der Vektorisierung, denn glibc-Autoren sind sicher :-)
Ciro Santilli 27 冠状 病 六四 事件 法轮功
3
Diese Version sollte schneller sein als die Version strlen + strncmp, wenn das Präfix nicht übereinstimmt, insbesondere wenn es bereits Unterschiede in den ersten Zeichen gibt.
dpi
1
^ Diese Optimierung würde nur gelten, wenn die Funktion inline ist.
Jim Balter
5

Ich bin kein Experte für das Schreiben von elegantem Code, aber ...

int prefix(const char *pre, const char *str)
{
    char cp;
    char cs;

    if (!*pre)
        return 1;

    while ((cp = *pre++) && (cs = *str++))
    {
        if (cp != cs)
            return 0;
    }

    if (!cs)
        return 0;

    return 1;
}
wj32
quelle
5

Verwenden Sie strstr()Funktion. Stra == strstr(stra, strb)

gscott
quelle
3
Das scheint etwas rückwärts zu sein - Sie werden die ganze Straße durchlaufen, obwohl aus einem sehr kurzen Anfangssegment klar sein sollte, ob strb ein Präfix ist oder nicht.
StasM
1
Vorzeitige Optimierung ist die Wurzel allen Übels. Ich denke, dies ist die beste Lösung, wenn es sich nicht um zeitkritischen Code oder lange Zeichenfolgen handelt.
Frank Buss
1
@ilw Es ist ein berühmtes Sprichwort berühmter Informatiker - google es. Es wird oft falsch angewendet (wie es hier ist) ... siehe joshbarczak.com/blog/?p=580
Jim Balter
2

Optimiert (v.2. - korrigiert):

uint32 startsWith( const void* prefix_, const void* str_ ) {
    uint8 _cp, _cs;
    const uint8* _pr = (uint8*) prefix_;
    const uint8* _str = (uint8*) str_;
    while ( ( _cs = *_str++ ) & ( _cp = *_pr++ ) ) {
        if ( _cp != _cs ) return 0;
    }
    return !_cp;
}
Zloten
quelle
2
Abstimmung negativ: startsWith("\2", "\1")gibt 1 zurück, gibt startsWith("\1", "\1")auch 1 zurück
thejh
Bei dieser Entscheidung werden keine Optimierungen in Clang verwendet, da keine Instrumente verwendet werden.
Socketpair
^ Intrinsics helfen hier nicht weiter, insbesondere wenn die Zielzeichenfolge viel länger als das Präfix ist.
Jim Balter
1

Da ich die akzeptierte Version ausgeführt habe und ein Problem mit einem sehr langen Str hatte, musste ich die folgende Logik hinzufügen:

bool longEnough(const char *str, int min_length) {
    int length = 0;
    while (str[length] && length < min_length)
        length++;
    if (length == min_length)
        return true;
    return false;
}

bool startsWith(const char *pre, const char *str) {
    size_t lenpre = strlen(pre);
    return longEnough(str, lenpre) ? strncmp(str, pre, lenpre) == 0 : false;
}
Jordanien
quelle
1

Oder eine Kombination der beiden Ansätze:

_Bool starts_with(const char *restrict string, const char *restrict prefix)
{
    char * const restrict prefix_end = prefix + 13;
    while (1)
    {
        if ( 0 == *prefix  )
            return 1;   
        if ( *prefix++ != *string++)
            return 0;
        if ( prefix_end <= prefix  )
            return 0 == strncmp(prefix, string, strlen(prefix));
    }  
}

BEARBEITEN: Der folgende Code funktioniert NICHT , da wenn strncmp 0 zurückgibt, nicht bekannt ist, ob eine abschließende 0 oder die Länge (block_size) erreicht wurde.

Eine weitere Idee ist der blockweise Vergleich. Wenn der Block nicht gleich ist, vergleichen Sie diesen Block mit der ursprünglichen Funktion:

_Bool starts_with_big(const char *restrict string, const char *restrict prefix)
{
    size_t block_size = 64;
    while (1)
    {
        if ( 0 != strncmp( string, prefix, block_size ) )
          return starts_with( string, prefix);
        string += block_size;
        prefix += block_size;
        if ( block_size < 4096 )
          block_size *= 2;
    }
}

Die Konstanten 13, 64, 4096sowie die Potenzierung der block_sizenur Vermutungen. Es müsste für die verwendeten Eingabedaten und Hardware ausgewählt werden.

shpc
quelle
Das sind gute Ideen. Beachten Sie jedoch, dass das erste Verhalten technisch undefiniert ist, wenn das Präfix kürzer als 12 Byte ist (13 einschließlich NUL), da der Sprachstandard das Ergebnis der Berechnung einer anderen Adresse außerhalb der Zeichenfolge als dem unmittelbar folgenden Byte nicht definiert.
Jim Balter
@ JimBalter: Könnten Sie eine Referenz hinzufügen? Wenn der Zeiger dereferenziert ist und nach der abschließenden 0 liegt, ist der Wert des verzögerten Zeigers undefiniert. Aber warum sollte die Adresse selbst undefiniert sein? Es ist nur eine Berechnung.
shpc
Es gab jedoch einen allgemeinen Fehler: Die block_sizeInkrementierung muss nach der Zeigerinkrementierung erfolgen. Jetzt behoben.
shpc