Funkcje StartWith () i EndWith () w PHP


1479

Jak napisać dwie funkcje, które pobiorą ciąg i zwrócą go, jeśli zaczyna się od określonego znaku / ciągu lub kończy się na nim?

Na przykład:

$str = '|apples}';

echo startsWith($str, '|'); //Returns true
echo endsWith($str, '}'); //Returns true

19
Zobacz klasę Laravela: StartWith () i endWith () dla dobrze przetestowanych metod. Napotkano przypadki Edge , więc powszechne stosowanie tego kodu jest zaletą.
Gras Double


3
Ostrzeżenie: większość odpowiedzi tutaj nie jest wiarygodna w kodowaniu wielobajtowym, takim jak UTF-8.
Álvaro González

W związku z powyższym komentarzem możesz upewnić się, że używasz najnowszej wersji (na dzień dzisiejszy 5.4 ). Warto zauważyć, że opensWith () został zoptymalizowany pod kątem dużych łańcuchów stogów siana.
Gras Double

Odpowiedzi:


1612
function startsWith($haystack, $needle)
{
     $length = strlen($needle);
     return (substr($haystack, 0, $length) === $needle);
}

function endsWith($haystack, $needle)
{
    $length = strlen($needle);
    if ($length == 0) {
        return true;
    }

    return (substr($haystack, -$length) === $needle);
}

Użyj tego, jeśli nie chcesz używać wyrażenia regularnego.


16
+1 To jest czystsze niż zaakceptowana odpowiedź. Ponadto $lengthnie jest potrzebny w ostatnim wierszu pliku endsWith().
za dużo php

13
Powiedziałbym, że końcoweWith ('foo', '') == false to poprawne zachowanie. Ponieważ foo nie kończy się niczym. „Foo” kończy się na „o”, „oo” i „Foo”.
MrHus

125
Końcówki można napisać znacznie krócej:return substr($haystack, -strlen($needle))===$needle;
Rok Kralj

12
Można uniknąć ifcałkowicie przekazując $lengthjako trzeci parametr substr: return (substr($haystack, -$length, $length);. To rozwiązuje przypadek $length == 0zwracając pusty ciąg, a nie całość $haystack.
mxxk

20
@MrHus Polecam korzystanie z wielobajtowych bezpiecznych funkcji, np. Mb_strlen i mb_substr
19Gerhard85 25.04.16

1024

Możesz użyć substr_comparefunkcji, aby sprawdzić początek i koniec:

function startsWith($haystack, $needle) {
    return substr_compare($haystack, $needle, 0, strlen($needle)) === 0;
}
function endsWith($haystack, $needle) {
    return substr_compare($haystack, $needle, -strlen($needle)) === 0;
}

To powinno być jedno z najszybszych rozwiązań na PHP 7 ( skrypt porównawczy ). Testowany na stogach siana o pojemności 8 KB, różnych długościach igieł oraz pełnych, częściowych i niepasujących skrzyniach. strncmpjest odrobinę szybszy na początku, ale nie może sprawdzić końca.


74
Ta odpowiedź trafiła do Daily WTF! : D Zobacz thedailywtf.com/articles/…
Wim ten Brink

Pamiętaj, że komentarze @DavidWallace i @FrancescoMM dotyczą starszej wersji tej odpowiedzi. Obecna odpowiedź wykorzystuje, strrposktóra (powinna) zawieść natychmiast, jeśli igła nie pasuje do początku stogu siana.
Salman A

2
Nie rozumiem Na podstawie php.net/manual/en/function.strrpos.php : „Jeśli wartość jest ujemna, wyszukiwanie rozpocznie się od tylu znaków od końca łańcucha, szukając wstecz.” To wydaje się wskazywać, że zaczynamy od znaku 0 (z powodu -strlength($haystack)) i szukamy stamtąd wstecz ? Czy to nie znaczy, że niczego nie przeszukujesz? Nie rozumiem też !== falseczęści tego. Zgaduję, że opiera się to na dziwactwie PHP, w którym niektóre wartości są „prawdziwe”, a inne „fałszywe”, ale jak to działa w tym przypadku?
Welbog

3
@Welbog: na przykład haystack = xxxyyyigła = yyyi korzystanie strrposz wyszukiwania rozpoczyna się od pierwszego x. Teraz nie mamy udany mecz tutaj (znaleziono x zamiast y) i nie możemy już iść do tyłu (jesteśmy na początku łańcucha) poszukiwanie nie natychmiast . O użyciu !== false- strrposw powyższym przykładzie zwróci 0 lub false, a nie inną wartość. Podobnie strposw powyższym przykładzie może zostać zwrócona $temp(oczekiwana pozycja) lub fałszywa. Poszedłem z !== falsekonsekwencją, ale można było użyć odpowiednio === 0i === $tempfunkcji.
Salman,

8
@spoo już ustalono, że strpos === 0 jest okropnym rozwiązaniem, jeśli stóg siana jest duży, a igła nie istnieje.
Salman A,

243

Zaktualizowano 23 sierpnia 2016 r

Funkcje

function substr_startswith($haystack, $needle) {
    return substr($haystack, 0, strlen($needle)) === $needle;
}

function preg_match_startswith($haystack, $needle) {
    return preg_match('~' . preg_quote($needle, '~') . '~A', $haystack) > 0;
}

function substr_compare_startswith($haystack, $needle) {
    return substr_compare($haystack, $needle, 0, strlen($needle)) === 0;
}

function strpos_startswith($haystack, $needle) {
    return strpos($haystack, $needle) === 0;
}

function strncmp_startswith($haystack, $needle) {
    return strncmp($haystack, $needle, strlen($needle)) === 0;
}

function strncmp_startswith2($haystack, $needle) {
    return $haystack[0] === $needle[0]
        ? strncmp($haystack, $needle, strlen($needle)) === 0
        : false;
}

Testy

echo 'generating tests';
for($i = 0; $i < 100000; ++$i) {
    if($i % 2500 === 0) echo '.';
    $test_cases[] = [
        random_bytes(random_int(1, 7000)),
        random_bytes(random_int(1, 3000)),
    ];
}
echo "done!\n";


$functions = ['substr_startswith', 'preg_match_startswith', 'substr_compare_startswith', 'strpos_startswith', 'strncmp_startswith', 'strncmp_startswith2'];
$results = [];

foreach($functions as $func) {
    $start = microtime(true);
    foreach($test_cases as $tc) {
        $func(...$tc);
    }
    $results[$func] = (microtime(true) - $start) * 1000;
}

asort($results);

foreach($results as $func => $time) {
    echo "$func: " . number_format($time, 1) . " ms\n";
}

Wyniki (PHP 7.0.9)

(Posortowane od najszybszego do najwolniejszego)

strncmp_startswith2: 40.2 ms
strncmp_startswith: 42.9 ms
substr_compare_startswith: 44.5 ms
substr_startswith: 48.4 ms
strpos_startswith: 138.7 ms
preg_match_startswith: 13,152.4 ms

Wyniki (PHP 5.3.29)

(Posortowane od najszybszego do najwolniejszego)

strncmp_startswith2: 477.9 ms
strpos_startswith: 522.1 ms
strncmp_startswith: 617.1 ms
substr_compare_startswith: 706.7 ms
substr_startswith: 756.8 ms
preg_match_startswith: 10,200.0 ms

openswith_benchmark.php


3
Jeśli łańcuchy nie są puste, jak w twoich testach, jest to w rzeczywistości (20-30%) szybsze: function startswith5b($haystack, $needle) {return ($haystack{0}==$needle{0})?strncmp($haystack, $needle, strlen($needle)) === 0:FALSE;}dodałem odpowiedź poniżej.
FrancescoMM,

3
@Jronny Ponieważ 110 to mniej niż 133 ... ??
mpen

2
Cholera, nie wiem, co wtedy przyszło mi do głowy. Chyba brak snu.
Jronny,

1
@mpen, w ogóle nie zauważyłem słonia :(
Visman

1
Te testy nie są dobre w testowaniu wydajności. To, co robisz, to używanie losowego łańcucha jako igły. W 99,99% przypadków nie będzie zgodności. Większość funkcji zostanie zakończona po dopasowaniu pierwszego bajtu. Co z przypadkami znalezienia dopasowania? Która funkcja zajmuje najmniej czasu, aby zakończyć udany mecz? Co z przypadkami, w których 99% igły pasuje, ale nie ostatnich kilku bajtów? Która funkcja zajmuje najmniej czasu, aby stwierdzić brak dopasowania?
Salman,

137

Wszystkie odpowiedzi do tej pory wydaje się robić mnóstwo niepotrzebnej pracy strlen calculations, string allocations (substr)itp 'strpos'i 'stripos'zwracają indeks pierwszego wystąpienia $needlew $haystack:

function startsWith($haystack,$needle,$case=true)
{
    if ($case)
        return strpos($haystack, $needle, 0) === 0;

    return stripos($haystack, $needle, 0) === 0;
}

function endsWith($haystack,$needle,$case=true)
{
    $expectedPosition = strlen($haystack) - strlen($needle);

    if ($case)
        return strrpos($haystack, $needle, 0) === $expectedPosition;

    return strripos($haystack, $needle, 0) === $expectedPosition;
}

2
endsWith()funkcja ma błąd. Pierwszą linią powinien być (bez -1): $expectedPosition = strlen($haystack) - strlen($needle);
Enrico Detoma

6
Funkcja strlen () nie jest niepotrzebna. W przypadku, gdy łańcuch nie zaczyna się od podanej igły, Twój kod niepotrzebnie skanuje cały stóg siana.
AppleGrew

5
@ Oznacz tak, sprawdzanie tylko początku jest DUŻO szybsze, szczególnie jeśli robisz coś takiego jak sprawdzanie typów MIME (lub dowolnego innego miejsca, w którym łańcuch musi być duży)
chacham15

2
@mark Zrobiłem kilka testów porównawczych ze stosem siana 1000 znaków i igłą 10 lub 800 znaków, a strpos było o 30% szybsze. Wykonaj swoje testy porównawcze, zanim stwierdzisz, że coś jest szybsze, czy nie ...
wdev

7
Należy poważnie rozważyć podając jak igły strpos($haystack, "$needle", 0)czy istnieje jakikolwiek szansa nie jest już łańcuch (na przykład jeśli pochodzi z json_decode()). W przeciwnym razie [nieparzyste] domyślne zachowanie strpos()może spowodować nieoczekiwane wyniki: „ Jeśli igła nie jest łańcuchem, jest konwertowana na liczbę całkowitą i stosowana jako wartość porządkowa znaku.
quietmint

46
function startsWith($haystack, $needle, $case = true) {
    if ($case) {
        return (strcmp(substr($haystack, 0, strlen($needle)), $needle) === 0);
    }
    return (strcasecmp(substr($haystack, 0, strlen($needle)), $needle) === 0);
}

function endsWith($haystack, $needle, $case = true) {
    if ($case) {
        return (strcmp(substr($haystack, strlen($haystack) - strlen($needle)), $needle) === 0);
    }
    return (strcasecmp(substr($haystack, strlen($haystack) - strlen($needle)), $needle) === 0);
}

Kredyt dla :

Sprawdź, czy łańcuch kończy się innym łańcuchem

Sprawdź, czy ciąg zaczyna się od innego ciągu


1
strtolower nie jest najlepszym sposobem na rozróżnianie wielkości liter. W niektórych lokalizacjach obudowa jest bardziej złożona niż tylko górna i dolna.
Sander Rijken

8
Widzę narzekanie i brak rozwiązania ... Jeśli chcesz powiedzieć, że jest źle, powinieneś podać przykład, jak powinno być.
KdgDev

2
@WebDevHobo: dlatego dodałem odpowiedź na dzień przed Twoim komentarzem. W przypadku twojego kodu strcasecmp rzeczywiście był właściwą rzeczą.
Sander Rijken,

29

Wyrażenia regularne działają powyżej, ale z innymi poprawkami również sugerowanymi powyżej:

 function startsWith($needle, $haystack) {
     return preg_match('/^' . preg_quote($needle, '/') . '/', $haystack);
 }

 function endsWith($needle, $haystack) {
     return preg_match('/' . preg_quote($needle, '/') . '$/', $haystack);
 }

2
w php dla operacji na łańcuchach kolejność parametrów to $ stóg siana, $ igła. funkcje te działają wstecz i działają jak funkcje tablicowe, w których kolejność to w rzeczywistości $ igła, $ stóg siana.
Andy

29

To pytanie ma już wiele odpowiedzi, ale w niektórych przypadkach możesz zadowolić się czymś prostszym niż wszystkie. Jeśli ciąg, którego szukasz, jest znany (zakodowany na stałe), możesz używać wyrażeń regularnych bez cudzysłowu itp.

Sprawdź, czy ciąg zaczyna się od „ABC”:

preg_match('/^ABC/', $myString); // "^" here means beginning of string

kończy się na „ABC”:

preg_match('/ABC$/', $myString); // "$" here means end of string

W moim prostym przypadku chciałem sprawdzić, czy ciąg kończy się ukośnikiem:

preg_match('#/$#', $myPath);   // Use "#" as delimiter instead of escaping slash

Zaleta: ponieważ jest bardzo krótka i prosta, nie trzeba definiować funkcji (takich jak endsWith()), jak pokazano powyżej.

Ale znowu - to nie jest rozwiązanie dla każdego przypadku, tylko ten bardzo konkretny.


nie musisz na stałe kodować łańcucha. Wyrażenie regularne może być dynamiczne.
Ryan

2
@ sama prawda, ale jeśli ciąg nie jest zakodowany na stałe, musisz go uciec. Obecnie na to pytanie udzielono 2 odpowiedzi. Jest to łatwe, ale nieco komplikuje kod. Chodzi mi więc o to, że w bardzo prostych przypadkach, w których możliwe jest kodowanie na stałe, możesz zachować prostotę.
noamtm

1
Nie musisz także uciekać przed ukośnikiem, możesz owinąć wyrażenie regularne inną postacią, na przykład @, aby ukośnik ( /) nie musiał uciekać. Zobacz przykład 3 tutaj: php.net/manual/en/function.preg-match.php .
cjbarth

Dzięki @cjbarth. Odpowiednio zmieniłem moją odpowiedź. BTW, „#” to przykład podany w php.net/manual/en/regexp.reference.delimiters.php w przypadku ukośnika.
noamtm

23

Jeśli prędkość jest dla Ciebie ważna, spróbuj tego (uważam, że jest to najszybsza metoda)

Działa tylko dla ciągów znaków i jeśli $ stóg siana ma tylko 1 znak

function startsWithChar($needle, $haystack)
{
   return ($needle[0] === $haystack);
}

function endsWithChar($needle, $haystack)
{
   return ($needle[strlen($needle) - 1] === $haystack);
}

$str='|apples}';
echo startsWithChar($str,'|'); //Returns true
echo endsWithChar($str,'}'); //Returns true
echo startsWithChar($str,'='); //Returns false
echo endsWithChar($str,'#'); //Returns false

1
jest to prawdopodobnie najbardziej wydajna odpowiedź, ponieważ nie używa żadnej funkcji jako dodatkowego, zwykłego łańcucha ...

Prawdopodobnie powinien sprawdzić, czy łańcuch ma co najmniej jeden znak i czy wymieniono dwa parametry
a1an

1
Twórczy. Igły zawierające stogi siana. BTW, jest trochę brzydki zanik z:, endsWithChar('','x')ale wynik jest poprawny
Tino

18

Oto dwie funkcje, które nie wprowadzają tymczasowego ciągu, które mogą być przydatne, gdy igły są znacznie duże:

function startsWith($haystack, $needle)
{
    return strncmp($haystack, $needle, strlen($needle)) === 0;
}

function endsWith($haystack, $needle)
{
    return $needle === '' || substr_compare($haystack, $needle, -strlen($needle)) === 0;
}

2
+1 Działa od PHP5.1 i najlepszej odpowiedzi IMHO. Ale endsWidthpowinien zrobić return $needle==='' || substr_compare(... więc działa zgodnie z oczekiwaniami -strlen($needle)===0, a bez poprawki endsWith('a','')zwracafalse
Tino

@Tino Dzięki ... Wydaje mi się, że to błąd substr_compare(), więc dodałem PR, aby to naprawić :)
Ja 21ck

3
Wywołanie endsWith('', 'foo')wyzwala Ostrzeżenie: „substr_compare (): Pozycja początkowa nie może przekroczyć początkowej długości łańcucha”. Może to kolejny błąd substr_compare(), ale aby tego uniknąć, potrzebujesz wstępnej kontroli, takiej jak ... || (strlen($needle) <= strlen($haystack) && substr_compare(...) === 0);
gx_

@gx_ Nie trzeba zwalniać z większą ilością kodu. Wystarczy użyć return $needle === '' || @substr_compare(..., aby ukryć to ostrzeżenie.
Tino,

17

Najszybsze rozwiązanie Rozwiązanie ():

# Checks if a string ends in a string
function endsWith($haystack, $needle) {
    return substr($haystack,-strlen($needle))===$needle;
}

Reper:

# This answer
function endsWith($haystack, $needle) {
    return substr($haystack,-strlen($needle))===$needle;
}

# Accepted answer
function endsWith2($haystack, $needle) {
    $length = strlen($needle);

    return $length === 0 ||
    (substr($haystack, -$length) === $needle);
}

# Second most-voted answer
function endsWith3($haystack, $needle) {
    // search forward starting from end minus needle length characters
    if ($needle === '') {
        return true;
    }
    $diff = \strlen($haystack) - \strlen($needle);
    return $diff >= 0 && strpos($haystack, $needle, $diff) !== false;
}

# Regex answer
function endsWith4($haystack, $needle) {
    return preg_match('/' . preg_quote($needle, '/') . '$/', $haystack);
}

function timedebug() {
    $test = 10000000;

    $time1 = microtime(true);
    for ($i=0; $i < $test; $i++) {
        $tmp = endsWith('TestShortcode', 'Shortcode');
    }
    $time2 = microtime(true);
    $result1 = $time2 - $time1;

    for ($i=0; $i < $test; $i++) {
        $tmp = endsWith2('TestShortcode', 'Shortcode');
    }
    $time3 = microtime(true);
    $result2 = $time3 - $time2;

    for ($i=0; $i < $test; $i++) {
        $tmp = endsWith3('TestShortcode', 'Shortcode');
    }
    $time4 = microtime(true);
    $result3 = $time4 - $time3;

    for ($i=0; $i < $test; $i++) {
        $tmp = endsWith4('TestShortcode', 'Shortcode');
    }
    $time5 = microtime(true);
    $result4 = $time5 - $time4;

    echo $test.'x endsWith: '.$result1.' seconds # This answer<br>';
    echo $test.'x endsWith2: '.$result4.' seconds # Accepted answer<br>';
    echo $test.'x endsWith3: '.$result2.' seconds # Second most voted answer<br>';
    echo $test.'x endsWith4: '.$result3.' seconds # Regex answer<br>';
    exit;
}
timedebug();

Wyniki testu:

10000000x endsWith: 1.5760900974274 seconds # This answer
10000000x endsWith2: 3.7102129459381 seconds # Accepted answer
10000000x endsWith3: 1.8731069564819 seconds # Second most voted answer
10000000x endsWith4: 2.1521229743958 seconds # Regex answer

3
+1 za poświęcenie czasu na porównanie różnych rozwiązań i ich porównanie! powinieneś również wspomnieć, jakiej wersji PHP użyłeś, ponieważ optymalizacje są dokonywane wraz z rozwojem języka! Widziałem radykalne usprawnienia funkcji porównywania ciągów z jednej wersji PHP do drugiej :)
Christophe Deliens

1
echo @ChristopheDeliens i jego prośba o dostarczenie wersji PHP. Uruchomiłem test na 7.3.2 i uzyskałem podobne wyniki FWIW.
Jeff

16

Zdaję sobie sprawę, że zostało to zakończone, ale możesz chcieć spojrzeć na strncmp, ponieważ pozwala on ustawić długość łańcucha do porównania, więc:

function startsWith($haystack, $needle, $case=true) {
    if ($case)
        return strncasecmp($haystack, $needle, strlen($needle)) == 0;
    else
        return strncmp($haystack, $needle, strlen($needle)) == 0;
}    

jak byś z tym skończył?
mpen

@Mark - możesz zobaczyć zaakceptowaną odpowiedź, ale wolę używać strncmp głównie dlatego, że uważam, że jest bezpieczniejszy.
James Black

Mam na myśli konkretnie strncmp. Nie można określić przesunięcia. Oznaczałoby to, że twoje cele z funkcją musiałyby całkowicie użyć innej metody.
mpen

@Mark - Dla EndWith Chciałbym po prostu użyć strrpos ( php.net/manual/en/function.strrpos.php ), ale ogólnie rzecz biorąc, za każdym razem, gdy używasz strcmp, strncmp jest prawdopodobnie bezpieczniejszą opcją.
James Black

11

Możesz użyć strposistrrpos

$bStartsWith = strpos($sHaystack, $sNeedle) == 0;
$bEndsWith = strrpos($sHaystack, $sNeedle) == strlen($sHaystack)-strlen($sNeedle);

1
Należy używać potrójne równych tutaj strpos($sHaystack, $sNeedle) == 0tak strpos($sHaystack, $sNeedle) === 0? Widzę błąd, gdy false == 0ocenia true.
Kalyan

11

Oto wielobajtowa bezpieczna wersja zaakceptowanej odpowiedzi, działa dobrze dla ciągów UTF-8:

function startsWith($haystack, $needle)
{
    $length = mb_strlen($needle, 'UTF-8');
    return (mb_substr($haystack, 0, $length, 'UTF-8') === $needle);
}

function endsWith($haystack, $needle)
{
    $length = mb_strlen($needle, 'UTF-8');
    return $length === 0 ||
        (mb_substr($haystack, -$length, $length, 'UTF-8') === $needle);
}

2
jestem prawie pewien, że to tylko marnowanie procesora. wszystko, co musisz sprawdzić, dla StarstWith i EndsWith, to po prostu sprawdzenie, czy bajty się zgadzają, i właśnie to robi zaakceptowana odpowiedź. ta 1 marnuje czas na obliczanie liczby znaków utf8 igły, a gdzie n-ty znak utf8 stogu siana jest .. myślę, że bez 100% pewności, to tylko strata procesora. czy możesz wymyślić rzeczywisty przypadek testowy, w którym zaakceptowana odpowiedź kończy się niepowodzeniem, a to nie?
hanshenrik

2
@hanshenrik - może się to zdarzyć przy okazji, w bardzo rzadkim przypadku, gdy szukasz łańcucha zawierającego te same bajty co UTF8, ale brakuje połowy ostatniego znaku. Na przykład, masz Unicode C5 91 (litera „ő”) i szukasz C5 (litera „Å”), nie powinno ono pasować. Z drugiej strony, jasne, dlaczego miałbyś przeszukiwać stóg siana utf w poszukiwaniu igły innej niż utf ... Ale w przypadku kontroli kuloodpornych należy to rozważyć.
dkellner

W startsWithpowinno być$length = mb_strlen($needle, 'UTF-8');
Thomas Kekeisen

2
@ThomasKekeisen Dzięki, naprawiłem to.
Vahid Amiri,

8

Krótkie i łatwe do zrozumienia jedno-linijki bez wyrażeń regularnych.

StartWith () jest prosty.

function startsWith($haystack, $needle) {
   return (strpos($haystack, $needle) === 0);
}

EndWith () używa nieco fantazyjnego i wolnego strrev ():

function endsWith($haystack, $needle) {
   return (strpos(strrev($haystack), strrev($needle)) === 0);
}

@FrancescoMM: strpos nie jest „właściwym narzędziem” ... Dlaczego? Jakie są zatem „odpowiednie narzędzia”? EDYCJA: Przeczytałem twoją odpowiedź poniżej. Myślałem, że programowanie jest jak wynalazek z wykorzystaniem posiadanych zasobów. Więc nie ma w tym nic dobrego ani złego ... tylko działa lub nie działa ... wydajność ma drugorzędne znaczenie.
Fr0zenFyr

„ponieważ jest to narzędzie do wyszukiwania, a nie do porównywania?” Cit. Aristoteles
FrancescoMM

7

Skupienie się na początkach, jeśli masz pewność, że łańcuchy nie są puste, dodanie testu dla pierwszego znaku, przed porównaniem, strlen itp., Nieco przyspiesza:

function startswith5b($haystack, $needle) {
    return ($haystack{0}==$needle{0})?strncmp($haystack, $needle, strlen($needle)) === 0:FALSE;
}

Jest w jakiś sposób (20% -30%) szybszy. Dodanie kolejnego testu char, np. $ Haystack {1} === $ igła {1}, wydaje się nie przyspieszać, może nawet spowolnić.

===wydaje się szybszy niż == Operator warunkowy (a)?b:cwydaje się szybszy niżif(a) b; else c;


Dla tych, którzy pytają „dlaczego nie użyć strpos?” nazywając inne rozwiązania „niepotrzebną pracą”


strpos jest szybki, ale nie jest odpowiednim narzędziem do tego zadania.

Aby zrozumieć, oto mała symulacja jako przykład:

Search a12345678c inside bcdefga12345678xbbbbb.....bbbbba12345678c

Co komputer robi „w środku”?

    With strccmp, etc...

    is a===b? NO
    return false



    With strpos

    is a===b? NO -- iterating in haysack
    is a===c? NO
    is a===d? NO
    ....
    is a===g? NO
    is a===g? NO
    is a===a? YES
    is 1===1? YES -- iterating in needle
    is 2===3? YES
    is 4===4? YES
    ....
    is 8===8? YES
    is c===x? NO: oh God,
    is a===1? NO -- iterating in haysack again
    is a===2? NO
    is a===3? NO
    is a===4? NO
    ....
    is a===x? NO
    is a===b? NO
    is a===b? NO
    is a===b? NO
    is a===b? NO
    is a===b? NO
    is a===b? NO
    is a===b? NO
    ...
    ... may many times...
    ...
    is a===b? NO
    is a===a? YES -- iterating in needle again
    is 1===1? YES
    is 2===3? YES
    is 4===4? YES
    is 8===8? YES
    is c===c? YES YES YES I have found the same string! yay!
    was it at position 0? NOPE
    What you mean NO? So the string I found is useless? YEs.
    Damn.
    return false

Zakładając, że strlen nie iteruje całego łańcucha (ale nawet w takim przypadku), nie jest to wcale wygodne.


Przyspieszenie następuje tylko wtedy, gdy pierwsze postacie są różne.
Ja͢ck

2
@Jack tak, oczywiście chodzi o to, że dzieje się to statystycznie, więc przyspieszenie wynosi ogólnie 20% -30% w całym zestawie testowym (włączając przypadki, w których nie jest inaczej). Zyskujesz dużo, gdy są różne, a tracą bardzo mało, gdy nie są. Średnio zyskujesz to 30% (różni się w zależności od zestawu, ale głównie zyskujesz prędkość w dużych testach)
FrancescoMM

„ale to nie jest odpowiednie narzędzie do tego zadania” … Jakieś cytaty?
Fr0zenFyr

1
WTF. Wymieniłem cały proces poniżej, kogo powinienem zacytować. Czy użyłbyś funkcji, która wyszukuje do końca łańcucha, aby powiedzieć, że pierwszy znak nie jest „a”? Czy to kogo to obchodzi? To nie jest właściwe narzędzie, ponieważ jest to narzędzie do wyszukiwania, a nie do porównywania, nie ma potrzeby cytowania Aristotelesa, aby stwierdzić oczywistość!
FrancescoMM

6

Mam nadzieję, że poniższa odpowiedź może być skuteczna, a także prosta:

$content = "The main string to search";
$search = "T";
//For compare the begining string with case insensitive. 
if(stripos($content, $search) === 0) echo 'Yes';
else echo 'No';

//For compare the begining string with case sensitive. 
if(strpos($content, $search) === 0) echo 'Yes';
else echo 'No';

//For compare the ending string with case insensitive. 
if(stripos(strrev($content), strrev($search)) === 0) echo 'Yes';
else echo 'No';

//For compare the ending string with case sensitive. 
if(strpos(strrev($content), strrev($search)) === 0) echo 'Yes';
else echo 'No';

6

Zazwyczaj ostatnio kończę na bibliotece takiej jak podkreślenie-php .

require_once("vendor/autoload.php"); //use if needed
use Underscore\Types\String; 

$str = "there is a string";
echo( String::startsWith($str, 'the') ); // 1
echo( String::endsWith($str, 'ring')); // 1   

Biblioteka jest pełna innych przydatnych funkcji.


6

Odpowiedź przez MPpl jest niezwykle dokładny, ale, niestety, pod warunkiem odniesienia ma bardzo istotny i niekorzystny nadzoru.

Ponieważ każdy bajt w igłach i stogach siana jest całkowicie losowy, prawdopodobieństwo, że para igły i stogu siana będzie się różnić na pierwszym bajcie wynosi 99,609375%, co oznacza, że ​​średnio około 99609 ze 100000 par będzie się różnić na pierwszym bajcie . Innymi słowy, test porównawczy jest mocno ukierunkowany na startswithimplementacje, które wyraźnie sprawdzają pierwszy bajt, jakstrncmp_startswith2 .

Jeśli zamiast tego implementowana jest pętla generująca testy, to:

echo 'generating tests';
for($i = 0; $i < 100000; ++$i) {
    if($i % 2500 === 0) echo '.';

    $haystack_length = random_int(1, 7000);
    $haystack = random_bytes($haystack_length);

    $needle_length = random_int(1, 3000);
    $overlap_length = min(random_int(0, $needle_length), $haystack_length);
    $needle = ($needle_length > $overlap_length) ?
        substr($haystack, 0, $overlap_length) . random_bytes($needle_length - $overlap_length) :
        substr($haystack, 0, $needle_length);

    $test_cases[] = [$haystack, $needle];
}
echo " done!<br />";

wyniki testu porównawczego przedstawiają nieco inną historię:

strncmp_startswith: 223.0 ms
substr_startswith: 228.0 ms
substr_compare_startswith: 238.0 ms
strncmp_startswith2: 253.0 ms
strpos_startswith: 349.0 ms
preg_match_startswith: 20,828.7 ms

Oczywiście ten test porównawczy może nadal nie być całkowicie bezstronny, ale testuje wydajność algorytmów, gdy podaje się częściowo pasujące igły.


5

w skrócie:

function startsWith($str, $needle){
   return substr($str, 0, strlen($needle)) === $needle;
}

function endsWith($str, $needle){
   $length = strlen($needle);
   return !$length || substr($str, - $length) === $needle;
}

5

Tylko zalecenie:

function startsWith($haystack,$needle) {
    if($needle==="") return true;
    if($haystack[0]<>$needle[0]) return false; // ------------------------- speed boost!
    return (0===substr_compare($haystack,$needle,0,strlen($needle)));
}

Ta dodatkowa linia, porównująca pierwszy znak ciągów, może sprawić, że fałszywy przypadek natychmiast powróci , dzięki czemu wiele twoich porównań będzie znacznie szybszych (7 razy szybszych, gdy mierzę). W prawdziwym przypadku za tę pojedynczą linię praktycznie nie płacisz żadnej ceny, więc myślę, że warto ją uwzględnić. (Ponadto w praktyce, gdy testujesz wiele ciągów dla określonej początkowej porcji, większość porównań zakończy się niepowodzeniem, ponieważ w typowym przypadku szukasz czegoś.)


2
Błąd w kodzie: startsWith("123", "0")dajetrue
Tino

Tak, źle! Sprawdzanie się zdarzyło. Przepraszam! (Chciałem tylko zilustrować tę koncepcję w wierszu 3)
dkellner,

4

substrFunkcja może powrócić falsew wielu przypadkach szczególnych, więc tutaj jest moja wersja, która zajmuje się tymi zagadnieniami:

function startsWith( $haystack, $needle ){
  return $needle === ''.substr( $haystack, 0, strlen( $needle )); // substr's false => empty string
}

function endsWith( $haystack, $needle ){
  $len = strlen( $needle );
  return $needle === ''.substr( $haystack, -$len, $len ); // ! len=0
}

Testy ( trueznaczy dobre):

var_dump( startsWith('',''));
var_dump( startsWith('1',''));
var_dump(!startsWith('','1'));
var_dump( startsWith('1','1'));
var_dump( startsWith('1234','12'));
var_dump(!startsWith('1234','34'));
var_dump(!startsWith('12','1234'));
var_dump(!startsWith('34','1234'));
var_dump('---');
var_dump( endsWith('',''));
var_dump( endsWith('1',''));
var_dump(!endsWith('','1'));
var_dump( endsWith('1','1'));
var_dump(!endsWith('1234','12'));
var_dump( endsWith('1234','34'));
var_dump(!endsWith('12','1234'));
var_dump(!endsWith('34','1234'));

substr_compareWarto również sprawdzić tę funkcję. http://www.php.net/manual/en/function.substr-compare.php



4

Zrobiłbym to w ten sposób

     function startWith($haystack,$needle){
              if(substr($haystack,0, strlen($needle))===$needle)
              return true;
        }

  function endWith($haystack,$needle){
              if(substr($haystack, -strlen($needle))===$needle)
              return true;
        }

Zapominanie o zwróceniu wartości false, jeśli nie pasuje. Błędny błąd, ponieważ wartość zwracana przez funkcję nie powinna być „zakładana”, ale wiem, o co chodzi, przynajmniej w porównaniu z innymi odpowiedziami.
Spoo

3

W oparciu o odpowiedź Jamesa Blacka, oto jego koniec W wersji:

function startsWith($haystack, $needle, $case=true) {
    if ($case)
        return strncmp($haystack, $needle, strlen($needle)) == 0;
    else
        return strncasecmp($haystack, $needle, strlen($needle)) == 0;
}

function endsWith($haystack, $needle, $case=true) {
     return startsWith(strrev($haystack),strrev($needle),$case);

}

Uwaga: zamieniłem część if-else dla funkcji Jamesa Blacka startWith, ponieważ strncasecmp jest w rzeczywistości rozróżniającą wielkość liter wersją strncmp.


2
Pamiętaj, że strrev()jest kreatywny, ale bardzo kosztowny, szczególnie jeśli masz ciągi powiedzmy ... 100 KB.
Alexis Wilke

Użyj ===zamiast, ==aby się upewnić. 0jest równy wielu rzeczom w PHP.
nawfal

3

Dlaczego nie następujące?

//How to check if a string begins with another string
$haystack = "valuehaystack";
$needle = "value";
if (strpos($haystack, $needle) === 0){
    echo "Found " . $needle . " at the beginning of " . $haystack . "!";
}

Wynik:

Znaleziono wartość na początku valuehaystack!

Pamiętaj, strposzwróci wartość false, jeśli igła nie została znaleziona w stogu siana, i zwróci 0, jeśli i tylko wtedy, gdy igła została znaleziona przy indeksie 0 (AKA początek).

A oto koniec:

$haystack = "valuehaystack";
$needle = "haystack";

//If index of the needle plus the length of the needle is the same length as the entire haystack.
if (strpos($haystack, $needle) + strlen($needle) === strlen($haystack)){
    echo "Found " . $needle . " at the end of " . $haystack . "!";
}

W tym scenariuszu nie ma potrzeby uruchamiania funkcji StartWith () as

(strpos($stringToSearch, $doesItStartWithThis) === 0)

zwróci dokładnie prawda lub fałsz.

Wydaje się dziwne, że jest to takie proste, gdy szaleją tu wszystkie dzikie funkcje.


3
Wydaje się dziwne, że jeśli szukasz „xy” wewnątrz ciągu „abcdefghijklmxyz” zamiast po prostu porównywać „x” z „a” i zwracać FAŁSZ, wyglądasz na każdą postać od „a” do „m”, a potem znajdujesz „xy” wewnątrz ciągu, a na końcu zwracasz FAŁSZ, ponieważ jego pozycja nie jest równa zero! To, co robisz, jest dziwne i dzikie niż jakakolwiek inna szalona funkcja tutaj.
FrancescoMM

Prostota polega na pisaniu, a nie na logice.
Kade Hafen

To nie tyle logika, ile możliwa optymalizacja, na którą wskazał Francsco. Używanie strpos()będzie wolne, chyba że będzie pasować. strncmp()w tym przypadku byłoby znacznie lepiej.
Alexis Wilke,

Kiedy wykonujesz takie funkcje na niskim poziomie, zwykle wybierasz rozwiązanie najbardziej zoptymalizowane pod kątem prędkości, bez względu na to, jak skomplikowane, ponieważ będzie to nazywane miliony razy. Każda mikrosekunda, którą tu zyskujesz lub tracisz, robi bardzo istotną różnicę. Więc lepiej podkręć piekło (a potem zapomnij o złożoności, teraz, gdy masz już tę funkcję), zamiast wybierać wygląd i stracić przerażającą ilość czasu później, gdy nawet nie wiesz, co poszło nie tak. Wyobraź sobie sprawdzanie ciągu 2 GB, który nie pasuje.
dkellner

3

Wiele wcześniejszych odpowiedzi będzie działać równie dobrze. Jest to jednak możliwie jak najkrótsze i możesz zrobić to, co chcesz. Po prostu oświadczasz, że chciałbyś, aby „zwrócił prawdę”. Dołączyłem więc rozwiązania zwracające wartość logiczną prawda / fałsz i tekstowe prawda / fałsz.

// boolean true/false
function startsWith($haystack, $needle)
{
    return strpos($haystack, $needle) === 0 ? 1 : 0;
}

function endsWith($haystack, $needle)
{
    return stripos($haystack, $needle) === 0 ? 1 : 0;
}


// textual true/false
function startsWith($haystack, $needle)
{
    return strpos($haystack, $needle) === 0 ? 'true' : 'false';
}

function endsWith($haystack, $needle)
{
    return stripos($haystack, $needle) === 0 ? 'true' : 'false';
}

Prawdziwe. Jednak Peter prosił o funkcję, która działałaby z ciągami znaków. Niemniej jednak zaktualizowałem moją odpowiedź, aby cię uspokoić.
wynshaft

Po edycji rozwiązanie jest teraz całkowicie przestarzałe. Zwraca 'true'i 'false'jako ciągi znaków, które mają truelogiczne znaczenie. Jest to jednak dobry wzór na coś takiego jak underhanded.xcott.com ;)
Tino

Cóż, Peter właśnie stwierdził, że chce, aby zwrócił się „prawdziwy”. Pomyślałem więc, że zwrócę to, o co prosił. Dodałem obie wersje, na wypadek, gdyby nie tego chciał.
wynshaft

2

Możesz także użyć wyrażeń regularnych:

function endsWith($haystack, $needle, $case=true) {
  return preg_match("/.*{$needle}$/" . (($case) ? "" : "i"), $haystack);
}

3
Igłę należy uciec preg_quote($needle, '/').
Timo Tijhof,

2

Pętla bez kopii i bez internowania:

function startsWith(string $string, string $start): bool
{
    return strrpos($string, $start, - strlen($string)) !== false;
}

function endsWith(string $string, string $end): bool
{
    return ($offset = strlen($string) - strlen($end)) >= 0 
    && strpos($string, $end, $offset) !== false;
}

powinno to być znacznie szybsze niż wdrożenie MrHus! mogę to
porównać

1

Oto wydajne rozwiązanie dla PHP 4. Możesz uzyskać szybsze wyniki, jeśli na PHP 5, używając substr_comparezamiast strcasecmp(substr(...)).

function stringBeginsWith($haystack, $beginning, $caseInsensitivity = false)
{
    if ($caseInsensitivity)
        return strncasecmp($haystack, $beginning, strlen($beginning)) === 0;
    else
        return strncmp($haystack, $beginning, strlen($beginning)) === 0;
}

function stringEndsWith($haystack, $ending, $caseInsensitivity = false)
{
    if ($caseInsensitivity)
        return strcasecmp(substr($haystack, strlen($haystack) - strlen($ending)), $haystack) === 0;
    else
        return strpos($haystack, $ending, strlen($haystack) - strlen($ending)) !== false;
}

0

Możesz użyć do tego funkcji fnmatch .

// Starts with.
fnmatch('prefix*', $haystack);
// Ends with.
fnmatch('*suffix', $haystack);

ostrzeżenie, nie binarnie bezpieczne, a nawet nie zabezpieczone przed igłami zawierającymi symbole wieloznaczne = /
hanshenrik
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.