如何在std::set中进行不区分大小写的插入或搜索字符串?
例如:
std::set<std::string> s;
s.insert("Hello");
s.insert("HELLO"); //not allowed, string already exists.
您需要定义一个自定义比较器:
struct InsensitiveCompare {
bool operator() (const std::string& a, const std::string& b) const {
return strcasecmp(a.c_str(), b.c_str()) < 0;
}
};
std::set<std::string, InsensitiveCompare> s;
如果strcasecmp
不可用,您可以尝试使用stricmp
或strcoll
。
std::string
以外的其他字符串类型(已经测试过包括 std::wstring
,std::string_view
,char const*
)。基本上任何定义了字符 范围 的东西都可以使用。
关键点在于使用 boost::as_literal
,它允许我们在比较器中统一处理以 null 结尾的字符数组、字符指针和范围。
通用代码("iset.h"):
#pragma once
#include <set>
#include <algorithm>
#include <boost/algorithm/string.hpp>
#include <boost/range/as_literal.hpp>
// Case-insensitive generic string comparator.
struct range_iless
{
template< typename InputRange1, typename InputRange2 >
bool operator()( InputRange1 const& r1, InputRange2 const& r2 ) const
{
// include the standard begin() and end() aswell as any custom overloads for ADL
using std::begin; using std::end;
// Treat null-terminated character arrays, character pointers and ranges uniformly.
// This just creates cheap iterator ranges (it doesn't copy container arguments)!
auto ir1 = boost::as_literal( r1 );
auto ir2 = boost::as_literal( r2 );
// Compare case-insensitively.
return std::lexicographical_compare(
begin( ir1 ), end( ir1 ),
begin( ir2 ), end( ir2 ),
boost::is_iless{} );
}
};
// Case-insensitive set for any Key that consists of a range of characters.
template< class Key, class Allocator = std::allocator<Key> >
using iset = std::set< Key, range_iless, Allocator >;
使用示例("main.cpp"):
#include "iset.h" // above header file
#include <iostream>
#include <string>
#include <string_view>
// Output range to stream.
template< typename InputRange, typename Stream, typename CharT >
void write_to( Stream& s, InputRange const& r, CharT const* sep )
{
for( auto const& elem : r )
s << elem << sep;
s << std::endl;
}
int main()
{
iset< std::string > s1{ "Hello", "HELLO", "world" };
iset< std::wstring > s2{ L"Hello", L"HELLO", L"world" };
iset< char const* > s3{ "Hello", "HELLO", "world" };
iset< std::string_view > s4{ "Hello", "HELLO", "world" };
write_to( std::cout, s1, " " );
write_to( std::wcout, s2, L" " );
write_to( std::cout, s3, " " );
write_to( std::cout, s4, " " );
}
根据我所读的,这比stricmp()更具可移植性,因为stricmp()实际上并不是标准库的一部分,而只是由大多数编译器供应商实现的。因此,以下是我的解决方案,只需自己编写。
#include <string>
#include <cctype>
#include <iostream>
#include <set>
struct caseInsensitiveLess
{
bool operator()(const std::string& x, const std::string& y)
{
unsigned int xs ( x.size() );
unsigned int ys ( y.size() );
unsigned int bound ( 0 );
if ( xs < ys )
bound = xs;
else
bound = ys;
{
unsigned int i = 0;
for (auto it1 = x.begin(), it2 = y.begin(); i < bound; ++i, ++it1, ++it2)
{
if (tolower(*it1) < tolower(*it2))
return true;
if (tolower(*it2) < tolower(*it1))
return false;
}
}
return false;
}
};
int main()
{
std::set<std::string, caseInsensitiveLess> ss1;
std::set<std::string> ss2;
ss1.insert("This is the first string");
ss1.insert("THIS IS THE FIRST STRING");
ss1.insert("THIS IS THE SECOND STRING");
ss1.insert("This IS THE SECOND STRING");
ss1.insert("This IS THE Third");
ss2.insert("this is the first string");
ss2.insert("this is the first string");
ss2.insert("this is the second string");
ss2.insert("this is the second string");
ss2.insert("this is the third");
for ( auto& i: ss1 )
std::cout << i << std::endl;
std::cout << std::endl;
for ( auto& i: ss2 )
std::cout << i << std::endl;
}
输出大小写不敏感集和普通集以相同的顺序显示:
This is the first string
THIS IS THE SECOND STRING
This IS THE Third
this is the first string
this is the second string
this is the third
tolower
无法实现不区分大小写的比较。这种不可能性的教科书例子是ΌΣΟΣ,它与 όσος(希腊语中的“多少”)在不区分大小写时相同。 - R. Martinho Fernandes