文字列をすべて大文字またはすべて小文字に変換せずにC++で大文字と小文字を区別しない文字列比較を行う最良の方法は何ですか?
メソッドが Unicode に対応しているかどうか、また移植性がどの程度かを示してください。
Boostには、このための便利なアルゴリズムが含まれています。
#include <boost/algorithm/string.hpp>
// Or, for fewer header dependencies:
//#include <boost/algorithm/string/predicate.hpp>
std::string str1 = "hello, world!";
std::string str2 = "HELLO, WORLD!";
if (boost::iequals(str1, str2))
{
// Strings are identical
}
標準を利用しますchar_traits
。aは実際には、またはより明示的にstd::string
はのtypedefであることを思い出してください。タイプは、文字の比較方法、コピー方法、キャスト方法などを記述します。必要なのは、新しい文字列をtypedefし、大文字と小文字を区別せずに比較する独自のカスタムを提供することだけです。std::basic_string<char>
std::basic_string<char, std::char_traits<char> >
char_traits
basic_string
char_traits
struct ci_char_traits : public char_traits<char> {
static bool eq(char c1, char c2) { return toupper(c1) == toupper(c2); }
static bool ne(char c1, char c2) { return toupper(c1) != toupper(c2); }
static bool lt(char c1, char c2) { return toupper(c1) < toupper(c2); }
static int compare(const char* s1, const char* s2, size_t n) {
while( n-- != 0 ) {
if( toupper(*s1) < toupper(*s2) ) return -1;
if( toupper(*s1) > toupper(*s2) ) return 1;
++s1; ++s2;
}
return 0;
}
static const char* find(const char* s, int n, char a) {
while( n-- > 0 && toupper(*s) != toupper(a) ) {
++s;
}
return s;
}
};
typedef std::basic_string<char, ci_char_traits> ci_string;
詳細は、今週の達人番号29にあります。
ブーストの問題は、ブーストとリンクして依存しなければならないことです。場合によっては簡単ではありません (例: Android)。
また、char_traits を使用すると、すべての比較で大文字と小文字が区別されなくなりますが、これは通常、必要なことではありません。
これで十分です。それは合理的に効率的でなければなりません。ただし、ユニコードなどは処理しません。
bool iequals(const string& a, const string& b)
{
unsigned int sz = a.size();
if (b.size() != sz)
return false;
for (unsigned int i = 0; i < sz; ++i)
if (tolower(a[i]) != tolower(b[i]))
return false;
return true;
}
更新: ボーナス C++14 バージョン ( #include <algorithm>
):
bool iequals(const string& a, const string& b)
{
return std::equal(a.begin(), a.end(),
b.begin(), b.end(),
[](char a, char b) {
return tolower(a) == tolower(b);
});
}
更新: を使用した C++20 バージョンstd::ranges
:
#include <ranges>
#include <algorithm>
#include <string>
bool iequals(const std::string_view& lhs, const std::string_view& rhs) {
auto to_lower{ std::ranges::views::transform(std::tolower) };
return std::ranges::equal(lhs | to_lower, rhs | to_lower);
}
POSIX システムを使用している場合は、strcasecmpを使用できます。ただし、この関数は標準 C の一部ではなく、Windows でも使用できません。これは、ロケールが POSIX である限り、8 ビット文字で大文字と小文字を区別しない比較を実行します。ロケールが POSIX でない場合、結果は未定義です (そのため、ローカライズされた比較が行われる場合と行われない場合があります)。ワイド文字に相当するものはありません。
それができない場合、多くの歴史的な C ライブラリの実装には、関数 stricmp() および strnicmp() があります。Windows 上の Visual C++ は、ANSI 標準の一部ではないため、アンダースコアを前に付けることでこれらすべての名前を変更しました。そのため、そのシステムでは_stricmp または _strnicmpと呼ばれます。一部のライブラリには、ワイド文字またはマルチバイトと同等の関数 (通常は、wcsicmp、mbcsicmp などの名前) が含まれている場合があります。
C と C++ はどちらも国際化の問題をほとんど知らないため、サードパーティのライブラリを使用する以外に、この問題を解決する方法はありません。C/C++ 用の堅牢なライブラリが必要な場合は、IBM ICU (International Components for Unicode) を調べてください。ICU は、Windows システムと Unix システムの両方に対応しています。
ばかげた大文字と小文字を区別しない比較または完全に正規化された Unicode 比較について話しているのですか?
愚かな比較は、同じかもしれないがバイナリが等しくない文字列を見つけません。
例:
U212B (ANGSTROM SIGN)
U0041 (LATIN CAPITAL LETTER A) + U030A (COMBINING RING ABOVE)
U00C5 (LATIN CAPITAL LETTER A WITH RING ABOVE).
すべて同等ですが、バイナリ表現も異なります。
とはいえ、特にハングル、タイ、その他のアジア言語をサポートする予定がある場合は、Unicode の正規化を読む必要があります。
また、IBM はほとんどの最適化された Unicode アルゴリズムの特許を取得し、公開しています。また、実装も維持しています: IBM ICU
非 Unicode バージョンについて最初に考えたのは、次のようなことです。
bool caseInsensitiveStringCompare(const string& str1, const string& str2) {
if (str1.size() != str2.size()) {
return false;
}
for (string::const_iterator c1 = str1.begin(), c2 = str2.begin(); c1 != str1.end(); ++c1, ++c2) {
if (tolower(static_cast<unsigned char>(*c1)) != tolower(static_cast<unsigned char>(*c2))) {
return false;
}
}
return true;
}
boost::iequals は文字列の場合、utf-8 互換ではありません。boost::localeを使用できます。
comparator<char,collator_base::secondary> cmpr;
cout << (cmpr(str1, str2) ? "str1 < str2" : "str1 >= str2") << endl;
strcasecmp
Unix またはstricmp
Windowsで使用できます。
これまでに言及されていないことの 1 つは、これらのメソッドで stl 文字列を使用している場合、最初に 2 つの文字列の長さを比較すると便利です。この情報は文字列クラスで既に利用できるためです。これにより、比較している 2 つの文字列が最初から同じ長さでさえない場合、コストのかかる文字列比較を行うことができなくなります。
私はすべての投稿から良い答えをまとめようとしているので、これを編集するのを手伝ってください:
これを行う方法は次のとおりです。文字列を変換し、Unicode フレンドリーではありませんが、移植可能である必要があります。これはプラスです。
bool caseInsensitiveStringCompare( const std::string& str1, const std::string& str2 ) {
std::string str1Cpy( str1 );
std::string str2Cpy( str2 );
std::transform( str1Cpy.begin(), str1Cpy.end(), str1Cpy.begin(), ::tolower );
std::transform( str2Cpy.begin(), str2Cpy.end(), str2Cpy.begin(), ::tolower );
return ( str1Cpy == str2Cpy );
}
私が読んだことから、stricmp() は実際には std ライブラリの一部ではなく、ほとんどのコンパイラ ベンダーによってのみ実装されているため、これは stricmp() よりも移植性があります。
真に Unicode に適した実装を得るには、std ライブラリの外に出なければならないようです。優れたサードパーティ ライブラリの 1 つは、IBM ICU (International Components for Unicode) です。
また、boost::iequalsは、この種の比較を行うためのかなり優れたユーティリティを提供します。
Unicode をサポートする Visual C++ 文字列関数: http://msdn.microsoft.com/en-us/library/cc194799.aspx
あなたがおそらく探しているものは_wcsnicmp
参考までに、null ターミネータstrcmp()
にstricmp()
ヒットするまで処理するだけなので、バッファ オーバーフローに対して脆弱です。_strncmp()
とを使用する方が安全_strnicmp()
です。
Boost.Stringライブラリには、大文字と小文字を区別しない比較などを行うための多くのアルゴリズムがあります。
独自のものを実装することもできますが、それが既に行われているのになぜ気にする必要がありますか?
私の基本的な大文字と小文字を区別しない文字列比較のニーズのために、外部ライブラリを使用する必要はありません。また、他のすべての文字列と互換性のない、大文字と小文字を区別しない特性を持つ別の文字列クラスも必要ありません。
だから私が思いついたのはこれです:
bool icasecmp(const string& l, const string& r)
{
return l.size() == r.size()
&& equal(l.cbegin(), l.cend(), r.cbegin(),
[](string::value_type l1, string::value_type r1)
{ return toupper(l1) == toupper(r1); });
}
bool icasecmp(const wstring& l, const wstring& r)
{
return l.size() == r.size()
&& equal(l.cbegin(), l.cend(), r.cbegin(),
[](wstring::value_type l1, wstring::value_type r1)
{ return towupper(l1) == towupper(r1); });
}
char 用の 1 つのオーバーロードと whar_t 用の別のオーバーロードを持つ単純な関数。非標準のものは使用しないため、どのプラットフォームでも問題ありません。
等値比較では、可変長エンコーディングや Unicode 正規化などの問題は考慮されませんが、basic_string は、とにかく私が知っていることをサポートしておらず、通常は問題になりません。
より洗練されたテキストの辞書編集が必要な場合は、Boost のようなサード パーティのライブラリを使用する必要がありますが、これは当然のことです。
Boost を使用せずにこれを行うには、C 文字列ポインターを取得して使用することで実行できc_str()
ますstrcasecmp
。
std::string str1 ="aBcD";
std::string str2 = "AbCd";;
if (strcasecmp(str1.c_str(), str2.c_str()) == 0)
{
//case insensitive equal
}
組み込みのstd::basic_stringメンバー関数を使用して比較や検索などを行うときに大文字と小文字を区別しないstd::stringを生成するために、std::basic_stringで使用する大文字と小文字を区別しないバージョンのchar_traitsを作成しました。
つまり、こういうことをしたかったのです。
std::string a = "Hello, World!";
std::string b = "hello, world!";
assert( a == b );
...どのstd::stringは処理できません。これが私の新しいchar_traitsの使用法です:
std::istring a = "Hello, World!";
std::istring b = "hello, world!";
assert( a == b );
...そしてこれが実装です:
/* ---
Case-Insensitive char_traits for std::string's
Use:
To declare a std::string which preserves case but ignores case in comparisons & search,
use the following syntax:
std::basic_string<char, char_traits_nocase<char> > noCaseString;
A typedef is declared below which simplifies this use for chars:
typedef std::basic_string<char, char_traits_nocase<char> > istring;
--- */
template<class C>
struct char_traits_nocase : public std::char_traits<C>
{
static bool eq( const C& c1, const C& c2 )
{
return ::toupper(c1) == ::toupper(c2);
}
static bool lt( const C& c1, const C& c2 )
{
return ::toupper(c1) < ::toupper(c2);
}
static int compare( const C* s1, const C* s2, size_t N )
{
return _strnicmp(s1, s2, N);
}
static const char* find( const C* s, size_t N, const C& a )
{
for( size_t i=0 ; i<N ; ++i )
{
if( ::toupper(s[i]) == ::toupper(a) )
return s+i ;
}
return 0 ;
}
static bool eq_int_type( const int_type& c1, const int_type& c2 )
{
return ::toupper(c1) == ::toupper(c2) ;
}
};
template<>
struct char_traits_nocase<wchar_t> : public std::char_traits<wchar_t>
{
static bool eq( const wchar_t& c1, const wchar_t& c2 )
{
return ::towupper(c1) == ::towupper(c2);
}
static bool lt( const wchar_t& c1, const wchar_t& c2 )
{
return ::towupper(c1) < ::towupper(c2);
}
static int compare( const wchar_t* s1, const wchar_t* s2, size_t N )
{
return _wcsnicmp(s1, s2, N);
}
static const wchar_t* find( const wchar_t* s, size_t N, const wchar_t& a )
{
for( size_t i=0 ; i<N ; ++i )
{
if( ::towupper(s[i]) == ::towupper(a) )
return s+i ;
}
return 0 ;
}
static bool eq_int_type( const int_type& c1, const int_type& c2 )
{
return ::towupper(c1) == ::towupper(c2) ;
}
};
typedef std::basic_string<char, char_traits_nocase<char> > istring;
typedef std::basic_string<wchar_t, char_traits_nocase<wchar_t> > iwstring;
既に存在する魔法の関数ではなくメソッドを探していると仮定すると、率直に言ってこれ以上の方法はありません。限られた文字セットに対して巧妙なトリックを使ってコード スニペットを作成することはできますが、結局のところ、ある時点で文字を変換する必要があります。
この変換の最善の方法は、比較の前に行うことです。これにより、実際の比較演算子は無視する必要があるエンコーディング スキームに関して、かなりの柔軟性が得られます。
もちろん、この変換を独自の文字列関数またはクラスの背後に「隠す」ことができますが、それでも比較の前に文字列を変換する必要があります。
International Components for Unicode ライブラリを使用して良い経験をしました。これらは非常に強力で、変換、ロケール サポート、日付と時刻のレンダリング、大文字と小文字のマッピング (これは望ましくないようです)、および照合のためのメソッドを提供します。これには、大文字と小文字およびアクセントを区別しない比較 (およびその他) が含まれます。ライブラリの C++ バージョンしか使用していませんが、Java バージョンもあるようです。
@Coincoin によって参照される正規化された比較を実行するためのメソッドが存在し、ロケールを考慮することさえできます。 "l" と "m" なので、"lz" < "ll" < "ma" です。
strcmp()
大文字と小文字を区別する、strcmpi()
またはstricmp()
大文字と小文字を区別しない比較にのみ使用してください。どちらもヘッダーファイルにあります<string.h>
フォーマット:
int strcmp(const char*,const char*); //for case sensitive
int strcmpi(const char*,const char*); //for case insensitive
使用法:
string a="apple",b="ApPlE",c="ball";
if(strcmpi(a.c_str(),b.c_str())==0) //(if it is a match it will return 0)
cout<<a<<" and "<<b<<" are the same"<<"\n";
if(strcmpi(a.c_str(),b.c_str()<0)
cout<<a[0]<<" comes before ball "<<b[0]<<", so "<<a<<" comes before "<<b;
出力
apple と ApPlE は同じです
a は b の前に来るので、リンゴはボールの前に来る
最終的に選択した方法についてのメモです。その方法にたまたまstrcmp
いくつかの回答が示唆する使用が含まれている場合:
strcmp
一般に、Unicode データでは機能しません。一般に、utf-8 などのバイトベースの Unicode エンコーディングでは動作しません。これは、strcmp
バイトごとの比較のみが行われ、utf-8 でエンコードされた Unicode コード ポイントは 1 バイトを超える可能性があるためです。適切に処理される唯一の特定の Unicode ケースstrcmp
は、バイトベースのエンコーディングでエンコードされた文字列に U+00FF より下のコード ポイントのみが含まれる場合です。この場合、バイトごとの比較で十分です。
2013 年の初めの時点で、IBM によって維持されている ICU プロジェクトは、これに対するかなり良い答えです。
ICU は、「業界標準を綿密に追跡する、完全でポータブルな Unicode ライブラリ」です。文字列比較の特定の問題については、 Collation オブジェクトが必要なことを行います。
Mozilla Project は、2012 年半ばに Firefox の国際化のために ICU を採用しました。ビルド システムやデータ ファイル サイズの問題など、エンジニアリングに関する議論は次の場所で追跡できます。
C++ (Windows でテスト済み) で 2 つの文字列を比較する簡単な方法は、_stricmpを使用することです
// Case insensitive (could use equivalent _stricmp)
result = _stricmp( string1, string2 );
std::string で使用する場合の例:
std::string s1 = string("Hello");
if ( _stricmp(s1.c_str(), "HELLO") == 0)
std::cout << "The string are equals.";
詳細については、https ://msdn.microsoft.com/it-it/library/e0z9k731.aspx をご覧ください。
上記のソリューションはcompareメソッドを使用しておらず、totalを再度実装していないように見えるので、これが私のソリューションであり、うまくいくことを願っています(うまくいきます)。
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
string tolow(string a)
{
for(unsigned int i=0;i<a.length();i++)
{
a[i]=tolower(a[i]);
}
return a;
}
int main()
{
string str1,str2;
cin>>str1>>str2;
int temp=tolow(str1).compare(tolow(str2));
if(temp>0)
cout<<1;
else if(temp==0)
cout<<0;
else
cout<<-1;
}
ソース文字列を他の文字列とより頻繁に比較する必要がある場合、洗練された解決策の 1 つは正規表現を使用することです。
std::wstring first = L"Test";
std::wstring second = L"TEST";
std::wregex pattern(first, std::wregex::icase);
bool isEqual = std::regex_match(second, pattern);
bool insensitive_c_compare(char A, char B){
static char mid_c = ('Z' + 'a') / 2 + 'Z';
static char up2lo = 'A' - 'a'; /// the offset between upper and lowers
if ('a' >= A and A >= 'z' or 'A' >= A and 'Z' >= A)
if ('a' >= B and B >= 'z' or 'A' >= B and 'Z' >= B)
/// check that the character is infact a letter
/// (trying to turn a 3 into an E would not be pretty!)
{
if (A > mid_c and B > mid_c or A < mid_c and B < mid_c)
{
return A == B;
}
else
{
if (A > mid_c)
A = A - 'a' + 'A';
if (B > mid_c)/// convert all uppercase letters to a lowercase ones
B = B - 'a' + 'A';
/// this could be changed to B = B + up2lo;
return A == B;
}
}
}
これはおそらくもっと効率的にすることができますが、これはすべてのビットがむき出しの大きなバージョンです。
それほどポータブルではありませんが、私のコンピューターにあるものは何でもうまく機能します(わかりません、私は言葉ではなく写真です)
小文字と大文字のみが異なる文字列を比較する簡単な方法は、ASCII 比較を行うことです。すべての大文字と小文字は、ASCII テーブルでは 32 ビット異なります。この情報を使用すると、次のようになります...
for( int i = 0; i < string2.length(); i++)
{
if (string1[i] == string2[i] || int(string1[i]) == int(string2[j])+32 ||int(string1[i]) == int(string2[i])-32)
{
count++;
continue;
}
else
{
break;
}
if(count == string2.length())
{
//then we have a match
}
}