Sprawdź, czy łańcuch kończy się na innym ciągu w C ++

270

Jak mogę się dowiedzieć, czy ciąg znaków kończy się na innym ciągu w C ++?

bardziej miękki
źródło

Odpowiedzi:

211

Po prostu porównaj ostatnie n znaków, używając std::string::compare:

#include <iostream>

bool hasEnding (std::string const &fullString, std::string const &ending) {
    if (fullString.length() >= ending.length()) {
        return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending));
    } else {
        return false;
    }
}

int main () {
    std::string test1 = "binary";
    std::string test2 = "unary";
    std::string test3 = "tertiary";
    std::string test4 = "ry";
    std::string ending = "nary";

    std::cout << hasEnding (test1, ending) << std::endl;
    std::cout << hasEnding (test2, ending) << std::endl;
    std::cout << hasEnding (test3, ending) << std::endl;
    std::cout << hasEnding (test4, ending) << std::endl;

    return 0;
}
kdt
źródło
Tak, to bez wątpienia najlepszy sposób na zrobienie tego.
Noldorin
3
Zawsze nienawidzę obliczania wskaźników podciągów, jest to bardzo podatne na zranienie ... Wolę iterować wstecz od końca obu ciągów, próbując znaleźć niedopasowanie.
xtofl
17
@Noldorin Nie zgadzam się. Jest to oczywiste - najlepszym sposobem na to jest skorzystanie z biblioteki. Szkoda, że ​​biblioteka C ++ Standard robi tak mało przydatnych rzeczy.
masterxilo,
1
@masterxilo Którą bibliotekę proponujesz rozwiązać w celu rozwiązania tego problemu i jak ta biblioteka jest lepszym wyborem niż (zasadniczo) funkcja jednowierszowa?
Brandin
33
@Brandin Ponieważ to taka podstawowa funkcjonalność. C ++ zmusza nas do wielokrotnego przeprogramowywania tych samych funkcji, które są dostarczane od razu po wyjęciu z pudełka w każdym innym nowoczesnym języku komputerowym. Fakt, że ludzie muszą przejść do stackoverflow, aby rozwiązać to pytanie, pokazuje, że istnieje PB.
Conchylicultor
175

Użyj tej funkcji:

inline bool ends_with(std::string const & value, std::string const & ending)
{
    if (ending.size() > value.size()) return false;
    return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
}
Joseph
źródło
3
Uwaga: MSVC10 nie lubi tego rozwiązania: std::equal(suffix.rbegin(), suffix.rend(), str.rbegin()W trybie debugowania wyświetla:_DEBUG_ERROR("string iterator not decrementable");
remi.chateauneu
154

Użyj boost::algorithm::ends_with(patrz np. Http://www.boost.org/doc/libs/1_34_0/doc/html/boost/algorithm/ends_with.html ):

#include <boost/algorithm/string/predicate.hpp>

// works with const char* 
assert(boost::algorithm::ends_with("mystring", "ing"));

// also works with std::string
std::string haystack("mystring");
std::string needle("ing");
assert(boost::algorithm::ends_with(haystack, needle));

std::string haystack2("ng");
assert(! boost::algorithm::ends_with(haystack2, needle));
Andre Holzner
źródło
83

Zauważ, że począwszy od c ++ 20 std :: string w końcu zapewni początek_with i koniec_with . Wygląda na to, że istnieje szansa, że ​​c ++ 30 łańcuchów w c ++ może w końcu stać się użytecznymi, jeśli nie czytasz tego z odległej przyszłości, możesz użyć tych początków Z / Końców Z:

#include <string>

static bool endsWith(const std::string& str, const std::string& suffix)
{
    return str.size() >= suffix.size() && 0 == str.compare(str.size()-suffix.size(), suffix.size(), suffix);
}

static bool startsWith(const std::string& str, const std::string& prefix)
{
    return str.size() >= prefix.size() && 0 == str.compare(0, prefix.size(), prefix);
}

i dodatkowe przeciążenia pomocnika:

static bool endsWith(const std::string& str, const char* suffix, unsigned suffixLen)
{
    return str.size() >= suffixLen && 0 == str.compare(str.size()-suffixLen, suffixLen, suffix, suffixLen);
}

static bool endsWith(const std::string& str, const char* suffix)
{
    return endsWith(str, suffix, std::string::traits_type::length(suffix));
}

static bool startsWith(const std::string& str, const char* prefix, unsigned prefixLen)
{
    return str.size() >= prefixLen && 0 == str.compare(0, prefixLen, prefix, prefixLen);
}

static bool startsWith(const std::string& str, const char* prefix)
{
    return startsWith(str, prefix, std::string::traits_type::length(prefix));
}

Ciągi IMO, c ++ są wyraźnie dysfunkcyjne i nie zostały stworzone do użycia w kodzie świata rzeczywistego. Ale jest nadzieja, że ​​przynajmniej będzie lepiej.

Pavel P.
źródło
2
Ponieważ str.compare nie zwraca wartości logicznej, testowanie „== 0” za pomocą operatora not („!”) Nie jest zbyt mądre, ponieważ może to być mylące dla czytelników. Dla jasności użyj „... && str.compare (...) == 0”.
Thomas Tempelmann
@Pavel Czy istnieje powód, aby nie używać std :: string :: find w metodach „opensWith”?
Maxime Oudot,
4
@MaximeOudot Oczywiście, że jest! Dlaczego chcesz przeszukać cały ciąg, jeśli chcesz wiedzieć, czy zaczyna się od czegoś? Innymi słowy, możesz skończyć z wyszukiwaniem ciągu o długości 100 mb, aby znaleźć kawałek na końcu, a następnie zignorować ten wynik, ponieważ nie ma go na początku łańcucha.
Pavel P
1
Plus „1” dla prognozy c ++ 30.
Innocent Bystander,
40

Wiem, że pytanie dotyczy C ++, ale jeśli ktoś potrzebuje dobrze zaprojektowanej funkcji C, aby to zrobić:


/*  returns 1 iff str ends with suffix  */
int str_ends_with(const char * str, const char * suffix) {

  if( str == NULL || suffix == NULL )
    return 0;

  size_t str_len = strlen(str);
  size_t suffix_len = strlen(suffix);

  if(suffix_len > str_len)
    return 0;

  return 0 == strncmp( str + str_len - suffix_len, suffix, suffix_len );
}

Tomek
źródło
25

std::mismatchMetoda może służyć temu celowi, gdy używany do iteracji wstecznej od końca obu ciągów:

const string sNoFruit = "ThisOneEndsOnNothingMuchFruitLike";
const string sOrange = "ThisOneEndsOnOrange";

const string sPattern = "Orange";

assert( mismatch( sPattern.rbegin(), sPattern.rend(), sNoFruit.rbegin() )
          .first != sPattern.rend() );

assert( mismatch( sPattern.rbegin(), sPattern.rend(), sOrange.rbegin() )
          .first == sPattern.rend() );
xtofl
źródło
3
+1. Nigdy wcześniej nie zauważyłem std :: mismatch () - zastanawiam się, co jeszcze jest w tym pliku nagłówkowym algorytmów, na który nigdy nie spojrzałem ...
j_random_hacker
3
Myślę, że to jest warte samodzielnego pytania SO: czy kiedykolwiek przeglądałeś dostępne funkcje STL?
xtofl
2
Zauważ, że ma to ten sam wymóg, co std::equal: musisz wcześniej sprawdzić, czy rzekomy przyrostek nie jest dłuższy niż szukany ciąg. Zaniedbanie sprawdzenia, które prowadzi do nieokreślonego zachowania.
Rob Kennedy,
18

Moim zdaniem najprostszym rozwiązaniem C ++ jest:

bool endsWith(const string& s, const string& suffix)
{
    return s.rfind(suffix) == std::abs(s.size()-suffix.size());
}
baziorek
źródło
10
Jest to raczej powolne, ponieważ przeszukasz cały ciąg szamiast testować tylko jego koniec!
Alexis Wilke,
2
@nodakai, jeśli zdarzy mi się mieć ciąg 1Mb, będzie to znacznie więcej niż nanosekundy.
Alexis Wilke,
Nie wydaje mi się, że… w każdym razie musi to zrobić strlen, a potem zaczyna patrzeć od końca.
LtWorf
2
@LtWorf std::string::size()jest operacją o stałym czasie; nie potrzebuje strlen.
Thomas
2
Jak to może być nawet rozważane rozwiązanie, gdy zawodzi w przypadku, gdy sufiks.size () == s.size () + 1. Fragment kodu pokazujący ten onlinegdb.com/S1ITVqKDL . Złożoność jest nieistotna, jeśli nie działa poprawnie we wszystkich przypadkach.
c0ntrol
10

Niech abędzie ciągiem i bciągiem, którego szukasz. Posługiwać sięa.substr aby uzyskać ostatnie n znaków ai porównać je do b (gdzie n jest długością b)

Albo użyj std::equal (dołącz <algorithm>)

Dawny:

bool EndsWith(const string& a, const string& b) {
    if (b.size() > a.size()) return false;
    return std::equal(a.begin() + a.size() - b.size(), a.end(), b.begin());
}
Dario
źródło
Jak mogę zwrócić wartość true, jeśli kończy się po moim ciągu ciągiem \ r lub \ n lub oba? dzięki!
miękki
@Dario: Twoje rozwiązanie wykorzystujące std :: równe () jest dobre, jedno nie używa substr () - chyba że używasz ciągów COW (i niewielu ludzi, jak sądzę), substr () oznacza utworzenie drugiej kopii części ciągu, co oznacza dynamiczny przydział pamięci. Może to się nie powieść, aw każdym razie oznacza to, że zużywa się więcej pamięci niż inne rozwiązania (i prawie na pewno wolniej niż inne rozwiązania).
j_random_hacker
4

Pozwól mi rozszerzyć rozwiązanie Josepha o wersję bez rozróżniania wielkości liter ( demo online )

static bool EndsWithCaseInsensitive(const std::string& value, const std::string& ending) {
    if (ending.size() > value.size()) {
        return false;
    }
    return std::equal(ending.rbegin(), ending.rend(), value.rbegin(),
        [](const char a, const char b) {
            return tolower(a) == tolower(b);
        }
    );
}
Niedźwiedź polarny
źródło
3

tak samo jak powyżej, oto moje rozwiązanie

 template<typename TString>
  inline bool starts_with(const TString& str, const TString& start) {
    if (start.size() > str.size()) return false;
    return str.compare(0, start.size(), start) == 0;
  }
  template<typename TString>
  inline bool ends_with(const TString& str, const TString& end) {
    if (end.size() > str.size()) return false;
    return std::equal(end.rbegin(), end.rend(), str.rbegin());
  }
dodjango
źródło
1
Dlaczego starts_withużywa „ciąg :: porównaj”? Dlaczego nie std::equal(start.begin(), start.end(), str.begin())?
Dmytro Ovdiienko
Tylko dlatego, że rozpoczyna się od pierwszego, którego potrzebowałem. end_with został dodany później.
dodjango,
3

inną opcją jest użycie wyrażenia regularnego. Poniższy kod powoduje, że wyszukiwanie jest niewrażliwe na wielkie / małe litery:

bool endsWithIgnoreCase(const std::string& str, const std::string& suffix) {
  return std::regex_search(str,
     std::regex(std::string(suffix) + "$", std::regex_constants::icase));
}

prawdopodobnie nie tak wydajny, ale łatwy do wdrożenia.

Julien Pilet
źródło
Jest to bardzo wygodne dla każdego z C ++ 11 lub wyższym.
Clare Macrae
Uwaga, wyrażenia regularne mogą być niesamowicie wolne w C ++!
mxmlnkn
Wyrażenie regularne dla tego jest jak ... Muszę to zagłosować. Nie zrobię tego, ale powinienem.
MK.
2

możesz użyć string :: rfind

Pełny przykład oparty na komentarzach:

bool EndsWith(string &str, string& key)
{
size_t keylen = key.length();
size_t strlen = str.length();

if(keylen =< strlen)
    return string::npos != str.rfind(key,strlen - keylen, keylen);
else return false;
}
Ahmed Said
źródło
3
-1. Tak, możesz go użyć, ale jest on niepotrzebnie powolny w przypadku, gdy łańcuch nie kończy się na dostarczonym zakończeniu - skanowanie będzie kontynuowane aż do początku łańcucha. Nie wspominasz również, że potrzebujesz kolejnego testu, aby upewnić się, że końcówka pasuje na końcu łańcucha , a nie w innym miejscu łańcucha.
j_random_hacker
Po prostu umieszczam link do potrzebnej funkcji i myślę, że bardzo łatwo jest to zrobić z dokumentacji str.rfind (key, str.length () - key.length (), key.length ());
Ahmed powiedział
OK, to wydajne - ale w takim przypadku string :: find () działałoby równie dobrze. Musisz także wspomnieć o przypadku, gdy key.length ()> str.length () - kod, który sugerujesz w komentarzu, zawiesza się w tym przypadku. Jeśli zaktualizujesz swoją odpowiedź o te informacje, upuszczę moje -1.
j_random_hacker
2

Sprawdź, czy str ma przyrostek , używając poniżej:

/*
Check string is end with extension/suffix
*/
int strEndWith(char* str, const char* suffix)
{
  size_t strLen = strlen(str);
  size_t suffixLen = strlen(suffix);
  if (suffixLen <= strLen) {
    return strncmp(str + strLen - suffixLen, suffix, suffixLen) == 0;
  }
  return 0;
}
James Yang
źródło
2

Użyj algorytmu std :: równo <algorithms>z odwrotną iteracją:

std::string LogExt = ".log";
if (std::equal(LogExt.rbegin(), LogExt.rend(), filename.rbegin())) {
   
}
Siergiej
źródło
2
Chociaż ten kod może dostarczyć rozwiązania pytania, lepiej jest dodać kontekst wyjaśniający, dlaczego / jak to działa. Może to pomóc przyszłym użytkownikom w nauce i zastosowaniu tej wiedzy do własnego kodu. Prawdopodobnie będziesz mieć pozytywne opinie od użytkowników w postaci pozytywnych opinii, gdy kod zostanie wyjaśniony.
borchvm
@borchvm, dodano wyjaśnienia, mam nadzieję, że to pomoże zrozumieć
Siergiej
1

Odnośnie odpowiedzi Grzegorza Baziora. Użyłem tej implementacji, ale oryginalna ma błąd (zwraca true, jeśli porównam „..” z „.so”). Proponuję zmodyfikowaną funkcję:

bool endsWith(const string& s, const string& suffix)
{
    return s.size() >= suffix.size() && s.rfind(suffix) == (s.size()-suffix.size());
}
Andrew123
źródło
1

Pomyślałem, że sensowne jest opublikowanie surowego rozwiązania, które nie korzysta z żadnych funkcji bibliotecznych ...

// Checks whether `str' ends with `suffix'
bool endsWith(const std::string& str, const std::string& suffix) {
    if (&suffix == &str) return true; // str and suffix are the same string
    if (suffix.length() > str.length()) return false;
    size_t delta = str.length() - suffix.length();
    for (size_t i = 0; i < suffix.length(); ++i) {
        if (suffix[i] != str[delta + i]) return false;
    }
    return true;
}

Dodając prosty std::tolowermożemy sprawić, że wielkość liter nie będzie uwzględniana

// Checks whether `str' ends with `suffix' ignoring case
bool endsWithIgnoreCase(const std::string& str, const std::string& suffix) {
    if (&suffix == &str) return true; // str and suffix are the same string
    if (suffix.length() > str.length()) return false;
    size_t delta = str.length() - suffix.length();
    for (size_t i = 0; i < suffix.length(); ++i) {
        if (std::tolower(suffix[i]) != std::tolower(str[delta + i])) return false;
    }
    return true;
}
cute_ptr
źródło
dzięki za dodanie tego. lekkie rozwiązania są zawsze świetne
ekkis
1

Znalazłem tę przyjemną odpowiedź na podobny problem „startWith”:

Jak sprawdzić, czy C ++ std :: string zaczyna się od określonego ciągu i przekonwertować podłańcuch na int?

Możesz zastosować rozwiązanie wyszukiwania tylko w ostatnim miejscu ciągu:

bool endsWith(const std::string& stack, const std::string& needle) {
    return stack.find(needle, stack.size() - needle.size()) != std::string::npos;
}

W ten sposób możesz zrobić to krótko, szybko, użyć standardowego c ++ i uczynić go czytelnym.

mls
źródło
0

Jeśli jesteś podobny do mnie i nie przepadasz za purytem w C ++, oto stara hybryda skool. Zaletą jest to, że ciągi znaków to więcej niż garść znaków, jak większośćmemcmp implementacji porównuje słowa maszynowe, gdy jest to możliwe.

Musisz kontrolować zestaw znaków. Na przykład, jeśli takie podejście jest używane z typem utf-8 lub wchar, istnieje pewna wada, ponieważ nie obsługuje mapowania znaków - np. Gdy dwa lub więcej znaków jest logicznie identycznych .

bool starts_with(std::string const & value, std::string const & prefix)
{
    size_t valueSize = value.size();
    size_t prefixSize = prefix.size();

    if (prefixSize > valueSize)
    {
        return false;
    }

    return memcmp(value.data(), prefix.data(), prefixSize) == 0;
}


bool ends_with(std::string const & value, std::string const & suffix)
{
    size_t valueSize = value.size();
    size_t suffixSize = suffix.size();

    if (suffixSize > valueSize)
    {
        return false;
    }

    const char * valuePtr = value.data() + valueSize - suffixSize;

    return memcmp(valuePtr, suffix.data(), suffixSize) == 0;
}
jws
źródło
0

Moje dwa centy:

bool endsWith(std::string str, std::string suffix)
{
   return str.find(suffix, str.size() - suffix.size()) != string::npos;
}
upuść stół
źródło