Descubra se a string termina com outra string em C ++

270

Como posso descobrir se uma string termina com outra string em C ++?

sofr
fonte

Respostas:

211

Basta comparar os últimos n caracteres usando 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
fonte
Sim, esta é a melhor maneira de fazê-lo, sem dúvida.
Noldorin
3
Eu sempre odeio calcular índices de substrings, é muito propenso a um ... Eu prefiro iterar para trás a partir do final de ambas as strings, tentando encontrar uma incompatibilidade.
Xtofl 18/05/09
17
@Oldoldin Eu não concordo. Este é um acéfalo - a melhor maneira de fazer isso é usar uma biblioteca. É uma pena que a biblioteca C ++ Standard faça tão poucas coisas úteis.
Masterxilo
1
@ masterxilo Que biblioteca você propõe para resolver esse problema e como é uma biblioteca melhor do que uma função de uma linha (basicamente)?
Brandin
33
@Brandin Porque é uma funcionalidade tão básica. O C ++ nos obriga a reprogramar repetidamente as mesmas funcionalidades que são fornecidas prontas para uso em qualquer outra linguagem de computador moderna. O fato de as pessoas precisarem ir ao stackoverflow para resolver essa questão mostra que há um pb.
Conchylicultor
175

Use esta função:

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
fonte
3
Cuidado que MSVC10 não gosta esta solução: std::equal(suffix.rbegin(), suffix.rend(), str.rbegin()No modo de depuração, ele lança:_DEBUG_ERROR("string iterator not decrementable");
remi.chateauneu
154

Use boost::algorithm::ends_with(consulte, por exemplo, 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
fonte
83

Observe que, a partir de c ++ 20 std :: string, finalmente será fornecido o start_with e ends_with . Parece que há uma chance de que, por c ++, 30 strings em c ++ possam finalmente se tornar utilizáveis, se você não estiver lendo isso em um futuro distante, poderá usar esses beginWith / endsWith:

#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);
}

e algumas sobrecargas auxiliares extras:

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

IMO, strings c ++ são claramente disfuncionais e não foram feitas para serem usadas no código do mundo real. Mas há uma esperança de que isso melhore, pelo menos.

Pavel P
fonte
2
Como str.compare não retorna um booleano, não é tão inteligente testar "== 0" usando o operador not ("!"), Pois isso pode ser confuso para os leitores. Por favor, use "... && str.compare (...) == 0" para maior clareza.
22417 Thomas Tempelmann
@Pavel Existe um motivo para não usar std :: string :: find nos métodos "beginWith"?
Maxime Oudot
4
@MaximeOudot Claro que há! Por que você deseja pesquisar toda a cadeia de caracteres se precisa saber se ela começa com alguma coisa? Em outras palavras, você pode procurar uma string de 100mb de comprimento para encontrar a peça no final e depois ignorar esse resultado, porque não está no início da string.
Pavel P
1
Mais "1" para previsão de c ++ 30.
Inocente espectador
40

Eu sei que a pergunta é para C ++, mas se alguém precisar de uma boa e antiga função C para fazer isso:


/*  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 );
}

Tom
fonte
25

O std::mismatchmétodo pode servir a esse propósito quando usado para iterar para trás a partir do final de ambas as strings:

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
fonte
3
+1. Eu tinha std :: nunca percebeu incompatibilidade () antes - eu me pergunto o que mais está em que os algoritmos de arquivo de cabeçalho que eu nunca olhou para ...
j_random_hacker
3
Eu acho que vale a pena uma pergunta SO por si própria: você já navegou pelas funções stl disponíveis?
Xtofl 18/05/09
2
Observe que isso tem o mesmo requisito que std::equal: você precisa verificar com antecedência se o suposto sufixo não é maior que a string em que está procurando. Negligenciar a verificação que leva a um comportamento indefinido.
Rob Kennedy
18

Na minha opinião mais simples, a solução C ++ é:

bool endsWith(const string& s, const string& suffix)
{
    return s.rfind(suffix) == std::abs(s.size()-suffix.size());
}
baziorek
fonte
10
Isso é bastante lento, pois você pesquisará toda a cadeia sde caracteres em vez de apenas testar o final dela!
Alexis Wilke #
2
@nodakai, se eu tiver uma string de 1Mb, será muito mais do que nanossegundos.
Alexis Wilke
Eu acho que não ... ele precisa fazer strlen em qualquer caso, e então começa a olhar do final.
LtWorf
2
@LtWorf std::string::size()é uma operação de tempo constante; não precisa strlen.
Thomas
2
Como pode ser essa solução considerada quando falha no caso quando suffix.size () == s.size () + 1. Fragmento de código mostrando isso onlinegdb.com/S1ITVqKDL . A complexidade é irrelevante se não funcionar corretamente em todos os casos.
c0ntrol 07/04
10

Seja auma string e ba string que você procura. Use a.substrpara obter os últimos n caracteres de ae compará-los com b (onde n é o comprimento de b)

Ou use std::equal(inclua <algorithm>)

Ex:

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
fonte
Como posso retornar true também se termina após minha string com \ r ou \ n ou ambos ??? obrigado!
sofr
@Dario: Sua solução usando std :: equal () é boa, a que usa substr () nem tanto - a menos que você esteja usando strings COW (e poucas pessoas acredito), substr () implica em criar uma segunda cópia parte da cadeia, implicando alocação dinâmica de memória. Isso pode falhar e, em qualquer caso, significa que mais memória é usada do que outras soluções (e é quase certamente mais lenta que outras soluções).
j_random_hacker
4

Deixe-me estender a solução de Joseph com a versão que não diferencia maiúsculas de minúsculas ( demonstração on-line )

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);
        }
    );
}
Urso polar
fonte
3

o mesmo que acima, aqui está a minha solução

 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
fonte
1
Por starts_withque usar 'string :: compare'? Por que não std::equal(start.begin(), start.end(), str.begin())?
Dmytro Ovdiienko
Só porque o begin_with foi o primeiro que eu precisava. ends_with foi adicionado mais tarde.
Dodjango
3

outra opção é usar regex. O código a seguir torna a pesquisa insensível a maiúsculas / minúsculas:

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

provavelmente não tão eficiente, mas fácil de implementar.

Julien Pilet
fonte
Para qualquer pessoa com C ++ 11 ou superior, isso é muito conveniente.
Clare Macrae
Cuidado, regexes podem ser incrivelmente lentas em C ++!
mxmlnkn
regex para isso é como ... eu preciso rebaixar isso. Não vou, mas devo.
MK.
2

você pode usar string :: rfind

O exemplo completo com base nos comentários:

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
fonte
3
-1. Sim, você pode usá-lo, mas é desnecessariamente lento no caso de a sequência não terminar com a final fornecida - a varredura continuará todo o caminho de volta ao início da sequência. Além disso, você não menciona que precisa de um teste subsequente para garantir que a finalização corresponda ao final da sequência , e não em qualquer outro local da sequência.
21139 j_random_hacker
Acabei de colocar o link da função necessária e acho muito fácil fazê-lo na documentação str.rfind (key, str.length () - key.length (), key.length ());
disse Ahmed
OK, isso é eficiente - mas nesse caso, string :: find () também funcionaria. Você também precisa mencionar o caso em que key.length ()> str.length () - o código que você sugere no seu comentário falhará neste caso. Se você atualizar sua resposta com essas informações, eu largarei meu -1.
21746 j_random_hacker
2

Verifique se str tem sufixo , usando abaixo:

/*
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
fonte
2

Use o algoritmo std :: equal <algorithms>com a iteração reversa:

std::string LogExt = ".log";
if (std::equal(LogExt.rbegin(), LogExt.rend(), filename.rbegin())) {
   
}
Sergei
fonte
2
Embora esse código possa fornecer uma solução para a pergunta, é melhor adicionar contexto ao porquê / como ele funciona. Isso pode ajudar os usuários futuros a aprender e aplicar esse conhecimento ao seu próprio código. Também é provável que você tenha um feedback positivo dos usuários na forma de upvotes, quando o código for explicado.
borchvm
@borchvm, acrescentou algumas explicações, espero que ajude a compreender
Sergei
1

Em relação à resposta de Grzegorz Bazior. Eu usei essa implementação, mas a original tem um bug (retorna true se eu comparar ".." com ".so"). Proponho função modificada:

bool endsWith(const string& s, const string& suffix)
{
    return s.size() >= suffix.size() && s.rfind(suffix) == (s.size()-suffix.size());
}
Andrew123
fonte
1

Eu pensei que faz sentido postar uma solução bruta que não usa nenhuma função de biblioteca ...

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

Adicionando um simples std::tolower, podemos tornar este caso insensível

// 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
fonte
obrigado por adicionar isso. soluções leves são sempre ótimas
ekkis 18/01
1

Encontrei esta boa resposta para o problema "startWith" semelhante:

Como verifico se um C ++ std :: string inicia com uma determinada string e converte uma substring em um int?

Você pode adotar a solução para pesquisar apenas no último lugar da string:

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

Dessa forma, você pode torná-lo curto, rápido, usar o c ++ padrão e torná-lo legível.

mls
fonte
0

Se você é como eu e não gosta de purismo em C ++, aqui está um velho híbrido de skool. Há alguma vantagem quando as strings são mais do que um punhado de caracteres, pois a maioria das memcmpimplementações compara palavras de máquina quando possível.

Você precisa estar no controle do conjunto de caracteres. Por exemplo, se essa abordagem for usada com utf-8 ou tipo wchar, há alguma desvantagem, pois ela não suporta o mapeamento de caracteres - por exemplo, quando dois ou mais caracteres são logicamente idênticos .

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
fonte
0

Meus dois centavos:

bool endsWith(std::string str, std::string suffix)
{
   return str.find(suffix, str.size() - suffix.size()) != string::npos;
}
soltar mesa
fonte