【问题标题】:Check if one string is a prefix of another检查一个字符串是否是另一个字符串的前缀
【发布时间】:2011-12-16 08:42:46
【问题描述】:

我有两个字符串要比较:String 和 String:。是否有一个库函数在传递这两个字符串时返回 true,但对于 String 和 OtherString 来说返回 false?

确切地说,我想知道一个字符串是否是另一个字符串的前缀。

【问题讨论】:

  • 用旧的string.compare()怎么样?
  • 你的意思是比较前 N 个字符?
  • @Donotalo 没关系,如果它为我做就好了,所以我不需要经历锻炼n 的麻烦。
  • 好吧,严格来说,满足您要求的一个函数是== 运算符。 ;-)
  • @FrerichRaabe:不,不是,他不想检查它们是否相同,而是要检查它们是否共享前缀

标签: c++ string-comparison prefix


【解决方案1】:

简单地说:

bool prefix(const std::string& a, const std::string& b) {
  if (a.size() > b.size()) {
    return a.substr(0,b.size()) == b;
  }
  else {
    return b.substr(0,a.size()) == a;
  }
}

C++ 不是 C,安全、简单、高效。

测试:

#include <string>
#include <iostream>

bool prefix(const std::string& a, const std::string& b);

int main() {
  const std::string t1 = "test";
  const std::string t2 = "testing";
  const std::string t3 = "hello";
  const std::string t4 = "hello world";
  std::cout << prefix(t1,t2) << "," << prefix(t2,t1) << std::endl;
  std::cout << prefix(t3,t4) << "," << prefix(t4,t3) << std::endl;
  std::cout << prefix(t1,t4) << "," << prefix(t4,t1) << std::endl;
  std::cout << prefix(t1,t3) << "," << prefix(t3,t1) << std::endl;

}

如果你有 C++17,你可以编写一个更好的版本,使用 std::string_view 代替:

#include <string>
#include <string_view>

bool prefix(const std::string& a, const std::string& b) {
  if (a.size() > b.size()) {
    return std::string_view(a.c_str(),b.size()) == b;
  }
  else {
    return std::string_view(b.c_str(),a.size()) == a;
  }
}

在 -O3 处使用 g++ 7,这将折叠为单个 memcmp 调用,这比旧版本有相当大的改进。

【讨论】:

  • 为什么要std::for_each + lambda,而不是噪音小得多的 for 循环?
  • @R.MartinhoFernandes - 已删除。我只是添加了那一点来显示用更大的列表调用它。
  • 此函数将报告空字符串包含所有其他字符串作为其前缀。对于前缀函数,使其对称是没有意义的。
  • 此方法复杂且效率低下。它总是创建可能涉及堆内存分配并可能抛出的临时字符串对象。
  • 如果我现在再写这个答案,我肯定会使用 string_view。
【解决方案2】:

在 C++20 之后,我们可以使用starts_with 来检查字符串是否以给定的前缀开头。

str.starts_with(prefix)

还有ends_with可以检查后缀

【讨论】:

    【解决方案3】:

    这样既高效又方便:

    str.compare(0, pre.size(), pre) == 0
    

    compare 速度很快,因为它使用了快速的traits::compare 方法并且不需要复制任何数据。

    在这里,它将比较 std::min(str.size(), pre.size()) 字符,但如果两个范围内的字符相等,它还会检查 pre 的长度,如果 pre 比这长,则返回非零值。

    请参阅 cplusplus.com 上的the documentation。

    我写了一个test program,它使用这段代码来比较命令行中给出的前缀和字符串。

    【讨论】:

    • 为什么需要a.size() &gt;= b.size()? compare() 也会处理。
    • 因为a.compare 会在到达a 的末尾时停止,并且不会查看b 的剩余字符。 b 不是 a 的前缀,如果它在末尾包含额外的字符。
    • 我已经更改了变量名以便于理解。
    • @ony 你是对的!不需要大小比较。我刚刚检查了cplusplus.com/reference/string/string/compare 的文档,并且compare 将返回0,前提是要比较的两个字符范围的长度相同。如果str 比pre 短,比较将返回一个负值(-1 在我的测试中)。我会编辑我的答案,但你应该分享功劳。但是,我能做的最好的就是投票赞成您的评论。
    • 这是最好的答案!
    【解决方案4】:
    bool IsPrefix(const std::string& prefix, const std::string& whole)
    {
      return whole.size() >= prefix.size() && whole.compare(0, prefix.size(), prefix) == 0;
    }
    

    【讨论】:

    • 这是先前提交的 answer 的副本,并使用了该答案中 cmets 确定为不必要的长度比较。
    • 我对@NeilMayhew 投了反对票,但经过进一步思考,我不同意这个反对票(不幸的是,现在被锁定了)。如果我没记错的话,初始测试是必要的(对于性能),并且该答案中的 cmets 说否则是错误的。请参阅我对该主题的回复。
    【解决方案5】:

    你可以用这个:

    c++14 或更低版本

    bool has_prefix
        (const std::string& str, const std::string& prefix)  {
        return str.find(prefix, 0) == 0;
    }
    

    c++17

    //it's a little faster
    auto has_prefix
        (const std::string& str, const std::string_view& prefix) -> decltype(str.find(prefix) == 0) {
        return str.find(prefix, 0) == 0;
    }
    

    【讨论】:

    • 如果字符串没有前缀并且str比prefix长,这会不会比其他一些方法慢很多?由于find() 方法将在str 中搜索prefix 的任何实例,即使它不是偏移量0。例如,检查“bbbbbbba”中的前缀“a”需要搜索整个字符串,找到最后的“a”,然后返回 false,因为它不在偏移量零处,而不是仅比较第一个字符后返回 false。
    • @TrentP 是的。改用 rfind() 可以解决这个问题,正如对这是一个重复的问题的公认答案中所建议的那样:stackoverflow.com/questions/1878001/…
    【解决方案6】:

    使用std::mismatch。将较短的字符串作为第一个迭代器范围传入,将较长的字符串作为第二个迭代器范围传入。返回的是一对迭代器,第一个是第一个范围内的迭代器,第二个是第二个范围内的迭代器。如果第一个是第一个范围的结尾,那么你知道短字符串是长字符串的前缀,例如

    std::string foo("foo");
    std::string foobar("foobar");
    
    auto res = std::mismatch(foo.begin(), foo.end(), foobar.begin());
    
    if (res.first == foo.end())
    {
      // foo is a prefix of foobar.
    }
    

    【讨论】:

    • +1,这实际上可以扩展到测试 share a prefix 而不是 is a prefix 通过将结果与begin() 进行比较而不是比结束(也可以通过减法获得公共前缀的实际长度)
    • +1,但是如果第二个字符串较短,这是很危险的,因为你会遍历它的结尾。因此需要检查foo.size() &lt;= foobar.size().
    • @Benoit, yip;让我感到困惑的是,他们可以很容易地接受第二个迭代器的结束,而我们不必在之前进行检查......
    • 这很简洁,但 James Kanze 使用 std::equal 的解决方案更简单。
    • @Benoit 注意,我认为您对大小的担忧已在 C++14 中得到解决。请参阅 cmets 上的 mismatch 的返回值。
    【解决方案7】:

    最简单的方法是使用 substr() 和 compare() 成员函数:

    string str = "Foobar";
    string prefix = "Foo";
    
    if(str.substr(0, prefix.size()).compare(prefix) == 0) cout<<"Found!";
    

    【讨论】:

    • substr 操作通常会复制数据,因此效率不高。
    • 如果你要使用substr(),你可以简单地写str.substr(0, prefix.size()) == prefix
    【解决方案8】:

    我认为strncmp 最接近您要查找的内容。

    不过,如果重新措辞,您可能正在寻找strstr(s2,s1)==s2,这不一定是最高效的方式。但是你不想锻炼n ;-)

    好的,好的,c++ 版本是!s1.find(s2)。

    好的,你可以让它更 C++,像这样:std::mismatch(s1.begin(),s1.end(),s2.begin()).first==s1.end()。

    【讨论】:

    • 问题被标记为C++,而不是C。
    • .c_str() 并不难调用 :)
    【解决方案9】:

    如果您可以合理地忽略任何多字节编码(例如,UTF-8),那么您可以为此使用strncmp:

    // Yields true if the string 's' starts with the string 't'.
    bool startsWith( const std::string &s, const std::string &t )
    {
        return strncmp( s.c_str(), t.c_str(), t.size() ) == 0;
    }
    

    如果您坚持使用花哨的 C++ 版本,您可以使用 std::equal 算法(另外一个好处是您的函数也适用于其他集合,而不仅仅是字符串):

    // Yields true if the string 's' starts with the string 't'.
    template <class T>
    bool startsWith( const T &s, const T &t )
    {
        return s.size() >= t.size() &&
               std::equal( t.begin(), t.end(), s.begin() );
    }
    

    【讨论】:

    • 使用您的 std::equal 解决方案,当 s 短于 t 时会发生什么?它似乎可以读到 s 的结尾。
    • @teambob:你是对的;我扩充了答案以检查两个字符串的大小。
    【解决方案10】:

    “查找”并检查位置 0 的结果有什么问题?

    string a = "String";
    string b = "String:";
    
    if(b.find(a) == 0)
    {
    // Prefix
    
    }
    else
    {
    // No Prefix
    }
    

    【讨论】:

    • find 搜索整个字符串,compare 做得更好。
    【解决方案11】:

    如果你知道哪个字符串更短,程序很简单,只需使用 std::equal 首先使用较短的字符串。如果你不这样做,一些东西 像下面这样应该工作:

    bool
    unorderIsPrefix( std::string const& lhs, std::string const& rhs )
    {
        return std::equal(
            lhs.begin(),
            lhs.begin() + std::min( lhs.size(), rhs.size() ),
            rhs.begin() );
    }
    

    【讨论】:

      【解决方案12】:

      如果在 str1 的索引 0 处找到整个 str2,则 str1.find(str2) 返回 0:

      #include <string>
      #include <iostream>
      
      // does str1 have str2 as prefix?
      bool StartsWith(const std::string& str1, const std::string& str2)
      {   
          return (str1.find(str2)) ? false : true;
      }
      
      // is one of the strings prefix of the another?
      bool IsOnePrefixOfAnother(const std::string& str1, const std::string& str2)
      {   
          return (str1.find(str2) && str2.find(str1)) ? false : true;
      }
      
      int main()
      {
          std::string str1("String");
          std::string str2("String:");
          std::string str3("OtherString");
      
          if(StartsWith(str2, str1))
          {
              std::cout << "str2 starts with str1" << std::endl;      
          }
          else
          {
              std::cout << "str2 does not start with str1" << std::endl;
          }
      
          if(StartsWith(str3, str1))
          {
              std::cout << "str3 starts with str1" << std::endl;      
          }
          else
          {
              std::cout << "str3 does not start with str1" << std::endl;
          }
      
              if(IsOnePrefixOfAnother(str2, str1))
              {
                  std::cout << "one is prefix of another" << std::endl;      
              }
              else
              {
                  std::cout << "one is not prefix of another" << std::endl;
              }
      
              if(IsOnePrefixOfAnother(str3, str1))
              {
                  std::cout << "one is prefix of another" << std::endl;      
              }
              else
              {
                  std::cout << "one is not prefix of another" << std::endl;
              }
      
          return 0;
      }
      

      输出:

        str2 starts with str1
        str3 does not start with str1
        one is prefix of another
        one is not prefix of another
      

      【讨论】:

        【解决方案13】:

        使用string::compare,您应该能够编写如下内容:

        bool match = (0==s1.compare(0, min(s1.length(), s2.length()), s2,0,min(s1.length(),s2.length())));

        或者,如果我们不想使用 length() 成员函数:

        bool isPrefix(string const& s1, string const&s2)
        {
            const char*p = s1.c_str();
            const char*q = s2.c_str();
            while (*p&&*q)
                if (*p++!=*q++)
                    return false;
            return true;
        }
        

        【讨论】:

        • 如果string1 很长,这可能是低效的——调用length() 是O(n) 并且不需要知道字符串的确切长度。你只关心它是否足够长。
        • .length() is O(n) ?您是否有机会查看character_traits 表?
        • @Frerich:我承认,我不知道。但话又说回来,在大多数当前编译器上可能是 O(1)。或者,您可以从头开始,然后比较字符,直到其中一个是 \0。
        • 在 C++11 中,length() 必须占用固定时间;在 C++03 中,它“应该”。
        • @FrerichRaabe: 基本原理 1) 字符串需要知道begin()和end()在常数时间内,迭代器是随机的,所以可以在常数时间内相减,区别在于大小字符串,它必须在恒定时间内已知。基本原理 2) 除非字符串是用 ropes 实现的(在 C++11 中被禁止,在任何 known 当前的标准库实现中都没有实现),内存是连续的,并且表示知道begin()和end()和知道size()是等价的,你需要存储三个中的两个,另一个可以在常数时间内计算。
        【解决方案14】:

        std::string(X).find(Y) 为零当且仅当Y 是X 的前缀

        【讨论】:

        • 这可能不是最有效的。编译器需要内联它,否则它也必须在非零偏移处搜索Y。
        • 这很简洁,但可能效率低下(想象一下,如果X 很长并且Y 不是 X 的前缀)。
        • @FrerichRaabe:这就是我自己对此发表评论的原因。一个好的优化器会发现与零的比较,发现比较对象对应于前面for循环中使用的索引变量,然后用if语句替换for循环。
        • 来自未来的消息:使用std::string_view :)
        猜你喜欢
        • 2013-09-06
        • 2019-05-11
        • 1970-01-01
        • 1970-01-01
        • 2013-05-12
        • 2013-03-13
        • 1970-01-01
        • 2010-10-13
        相关资源
        最近更新 更多