检查一个字符串是不是是另一个字符串的前缀
Posted
技术标签:
【中文标题】检查一个字符串是不是是另一个字符串的前缀【英文标题】: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:不,不是,他不想检查它们是否相同,而是要检查它们是否共享前缀
【参考方案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() >= 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() 可以解决这个问题,正如对这是一个重复的问题的公认答案中所建议的那样:***.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() <= 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
:)以上是关于检查一个字符串是不是是另一个字符串的前缀的主要内容,如果未能解决你的问题,请参考以下文章
如何检查一个字符串“StartsWith”是不是是另一个字符串?
如何检查一个字符串“StartsWith”是不是是另一个字符串?