How can I sort a std::list with case sensitive elements?
This is my current code:
#include <list>
#include <string>
using std::string;
using std::list;
int main()
{
list <string> list_;
list_.push_back("C");
list_.push_back("a");
list_.push_back("b");
list_.sort();
}
Does the sort()
开发者_Go百科 function sort the elements according to their character codes? I want the result here to be a b C
after the sorting is done.
Case-insensitive character comparisons are tricky if you want to support characters from other languages. That's why it's a good idea to do them in a locale-sensible manner:
struct char_iless
: public std::binary_function<char, char, bool>
{
std::locale loc;
char_iless(std::locale const & loc=std::locale()) : loc(loc)
{
}
bool operator()(char a, char b) const
{
return std::tolower(a, loc) < std::tolower(b, loc);
}
};
This is how you use this class to compare two chars:
char_iless('a', 'b', my_locale);
Just use std::locale()
as my_locale
if you want to use the one that's set as default.
If you can use Boost then there is am is_iless
functor in the String Algorithms library which does the same thing.
Extending this from comparing chars to strings is easy thanks to std::lexicographical_compare
:
struct str_iless
: public std::binary_function<std::string, std::string, bool>
{
std::locale loc;
str_iless(std::locale const & loc=std::locale()) : loc(loc)
{
}
bool operator()(std::string const & a, std::string const & b) const
{
return std::lexicographical_compare(
a.begin(), a.end(),
b.begin(), b.end(),
char_iless(loc)
);
}
};
Now you have all that it's required to solve your problem:
int main()
{
std::list<std::string> list;
list.push_back("C");
list.push_back("a");
list.push_back("b");
// Sort using default locale
list.sort(str_iless());
// Sort using French locale
// (warning: this locale format string is MS specific)
std::locale loc("French_France.1252");
list.sort(str_iless(loc));
}
The default comparator (<
) using the default char_traits< char >
will sort your list as C a b
.
See list::sort.
In order to achieve the desired order a b C
you can either:
- compose your list of
string
types with customchar_traits
, or provide an instance of a custom string comparator to
sort
, e.g.bool istring_less(const string& lhs, const string& rhs) { string::const_iterator \ lb = lhs.begin(), le = lhs.end(), rb = rhs.begin(), re = rhs.end(); const char lc, rc; for ( ; lb != le && rb != re; ++lb, ++rb) { lc = tolower(*lb); rc = tolower(*rb); if (*lc < *rc) return true; if (*lc > *rc) return false; } // if rhs is longer than lhs then lhs<rhs return (rb != re); } ... list.sort(istring_less);
Here are what I consider to be some cleaner and one significantly faster alternative:
#include <string>
#include <cstring>
#include <iostream>
#include <boost/algorithm/string.hpp>
using std::string;
using std::list;
using std::cout;
using std::endl;
using namespace boost::algorithm;
// recommended in Meyers, Effective STL when internationalization and embedded
// NULLs aren't an issue. Much faster than the STL or Boost lex versions.
struct ciLessLibC : public std::binary_function<string, string, bool> {
bool operator()(const string &lhs, const string &rhs) const {
return strcasecmp(lhs.c_str(), rhs.c_str()) < 0 ? 1 : 0;
}
};
// If you need sorting according to the default system local
struct ciLessBoost : std::binary_function<std::string, std::string, bool>
{
bool operator() (const std::string & s1, const std::string & s2) const {
return lexicographical_compare(s1, s2, is_iless());
}
};
int main(void) {
list <string> list_;
list_.push_back("C");
list_.push_back("a");
list_.push_back("b");
list_.sort(ciLessLibC());
list_.sort(ciLessBoost());
return 0;
}
yes
you can check the following code to use a custom comparator
Since C++11, you can also use a lambda expression instead of defining a comparator function/struct:
#include <list>
#include <string>
#include <cstring>
#include <iostream>
using namespace std;
int main()
{
list<string> list_;
list_.emplace_back("C");
list_.emplace_back("a");
list_.emplace_back("b");
list_.sort([](const string& a, const string& b) {
return (strcasecmp(a.c_str(), b.c_str()) < 0);
});
for (auto const &str : list_)
cout << str << endl;
return 0;
}
Output:
a
b
C
Note: The function strcasecmp()
(as also suggested in the answer by @RobertS.Barnes) is not in the C++ standard and, therefore, not available on every system. For example, if you are using Visual Studio, you could use _stricmp()
instead.
If internationalization is an issue for you, then you can apply a locale as @Manuel did in his answer.
Code on Ideone
精彩评论