Sortierung eines Vektors mit Umlauten und Klein-/Großbuchstaben
-
Hallo,
ich habe eine Struktur mit string-Variablen, aus der ich einen Vektor erzeuge. Diesen Vektor fülle ich und dann sortiere ich ihn mittels Definition des Standard-Operators < nach einer der string-Variablen mit der sort()-Funktion. Das klappt so weit auch schon, leider befinden sich jedoch Wörter mit Umlauten sowie kleingeschriebene Wörter stets am Ende der Liste. Diese sollten jedoch innerhalb der Liste korrekt einsortiert werden.
Wie lässt sich das am einfachsten bewerkstelligen?
Gruß,
Jan
-
indem du für den comparator für sort lexicographical_compare mit einem entsprechenden character-Comparator benutzt. den könntest du vermutlich mit dem collate-facet der richtigen locale hinbekommen.
http://www.cplusplus.com/reference/algorithm/lexicographical_compare/
http://www.cplusplus.com/reference/std/locale/collate/
-
Hallo pumuckl...lexicographical_compare verstehe ich ja halbwegs, zumindest sortiert das Programm jetzt anders(Umlaute am Anfang der Liste anstatt am Ende), die kleinbuchstaben sind aber immer noch ganz hinten:
return lexicographical_compare(lhs.Bezeichnung.begin(), lhs.Bezeichnung.end(), rhs.Bezeichnung.begin(), rhs.Bezeichnung.end());
Aber wie genau funktioniert das mit dem collate? Ich habe zwar einen Beispielcode mit compare() gefunden, aber das Programm sortiert dann irgendwie völig kreuz und quer...
locale loc; const collate<char>& coll = use_facet<collate<char> >(loc); return coll.compare(lhs.Bezeichnung.data(), lhs.Bezeichnung.data()+lhs.Bezeichnung.length(), rhs.Bezeichnung.data(), rhs.Bezeichnung.data()+rhs.Bezeichnung.length());
Irgendwie ist mir noch nicht so ganz klar, wie ich meine Vektoren-Strings sortieren kann...
Gruß,
Jan
-
Mr. Speck schrieb:
Hallo pumuckl...lexicographical_compare verstehe ich ja halbwegs, zumindest sortiert das Programm jetzt anders(Umlaute am Anfang der Liste anstatt am Ende), die kleinbuchstaben sind aber immer noch ganz hinten:
return lexicographical_compare(lhs.Bezeichnung.begin(), lhs.Bezeichnung.end(), rhs.Bezeichnung.begin(), rhs.Bezeichnung.end());
Aber wie genau funktioniert das mit dem collate? Ich habe zwar einen Beispielcode mit compare() gefunden, aber das Programm sortiert dann irgendwie völig kreuz und quer...
locale loc; const collate<char>& coll = use_facet<collate<char> >(loc); return coll.compare(lhs.Bezeichnung.data(), lhs.Bezeichnung.data()+lhs.Bezeichnung.length(), rhs.Bezeichnung.data(), rhs.Bezeichnung.data()+rhs.Bezeichnung.length());
Irgendwie ist mir noch nicht so ganz klar, wie ich meine Vektoren-Strings sortieren kann...
Gruß,
Jandann guckst du noch mal den Link an und siehst, dass du die Fkt-Überladung nehmen musst:
template <class InputIterator1, class InputIterator2, class Compare> bool lexicographical_compare ( InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2, Compare comp );
und comp dann halt so aussieht:
#include <string> //std::char_traits #include <locale> //std::locale -> std::islower, std::isupper, std::tolower, std::toupper template<typename TChar, typename TCharTraits = std::char_traits<TChar>, typename TLocale = std::locale > struct compare_ignoring_case { private: static bool is_lower(TChar value) { return std::islower(value, locale_type()); } static bool is_upper(TChar value) { return std::isupper(value, locale_type()); } static void make_lower(TChar &val) { val = std::tolower(val, locale_type()); } static void make_upper(TChar &val) { val = std::toupper(val, locale_type()); } static void make_compareable(TChar &lhs, TChar &rhs) { if(islower(lhs)) { if(isupper(rhs)) make_lower(rhs); return; } if(isupper(lhs)) { if(islower(rhs)) make_upper(rhs); return; } } public: typedef TCharTraits traits_type; typedef TLocale locale_type; bool operator() (TChar lhs, TChar rhs) const { make_compareable(lhs, rhs); return traits_type::lt(lhs, rhs); } };
anwendung:
#include <algorithm> #include <cstdlib> //für quick and dirty konsole offen halten #include <iostream> #include <string> //#include "my_string_comparer.h" bool string_lesser(const std::string &lhs, const std::string &rhs) { return lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), compare_ignoring_case<char>()); } int main() { std::string a = "ASD", b = "asd"; std::cout << std::boolalpha << string_lesser(a, b) << " == " << string_lesser(b, a) << std::endl; system("PAUSE"); //nicht nachmachen, weil unportabel etc - siehe konsole faq }
MSVC schrieb:
false == false
sollte also funktionieren ;o)
mir ist auf die schnelle auch kein (guter) kürzerer weg eingefallen, weil ich nicht sicher war, ob es sinnvoll ist nen eigenes
char_traits
zu schreiben(und halt vonstd::char_traits
public erben lassen und dann die compare-fkt zu überdecken) - aber wenn groß und kleinschreibung interessiert(was ich angenommen habe), könnte es hier imho probleme geben)bb
-
habs gerad noch mal angeguckt(wollte ma gucken, was
ß,S
und wasS,ß
ergibt) und gesehen, dass ich nen ziemlich großen fehler drin hatte xD
islower aufgerufen und wollte is_lower. und außerdem das locale-zeugs nicht berücksichtigt xD
also müssen wir immer kleinbuchstaben nehmen, weil a<b && b<a doof ist
also jz der richtige code(nur die klasse, am aufruf ändert sich nix):#include <string> //std::char_traits #include <locale> //std::locale -> std::islower, std::isupper, std::tolower, std::toupper template<typename TChar, typename TCharTraits = std::char_traits<TChar>, typename TLocale = std::locale > struct compare_ignoring_case { public: static bool uppercase; typedef TCharTraits traits_type; typedef TLocale locale_type; explicit compare_ignoring_case(const locale_type& locale = locale_type()) : locale(locale) {} bool operator() (TChar lhs, TChar rhs) const { make_compareable(lhs, rhs); return traits_type::lt(lhs, rhs); } private: locale_type locale; void make_compareable(TChar lhs, TChar rhs) const { if(uppercase) make_compareable_upper(lhs, rhs); else make_compareable_lower(lhs, rhs); } void make_compareable_upper(TChar lhs, TChar rhs) const { if(is_lower(lhs)) make_upper(lhs); if(is_lower(rhs)) make_upper(rhs); } void make_compareable_lower(TChar lhs, TChar rhs) const { if(is_upper(lhs)) make_lower(lhs); if(is_upper(rhs)) make_upper(rhs); } bool is_lower(TChar value) const { return std::islower(value, locale); } bool is_upper(TChar value) const { return std::isupper(value, locale); } void make_lower(TChar &val) const { val = std::tolower(val, locale); } void make_upper(TChar &val) const { val = std::toupper(val, locale); } }; template<typename C, typename T, typename L> bool compare_ignoring_case<C, T, L>::uppercase = true;
uppercase
gibts, weil es auch nicht schaded, es einstellen zu können...
der msvc9 optimiert es auch vollkommen raus - selbst wenn ich es später noch mal (anders) setze.
ist nur doof, dass man es für jedes template einzeln setzen muss
eigtl würd ichs wieder rausnehmen - aber hier schadet es ja nicht, es mitzuposten^^randbemerkung:
('ß', 's')
kann auf verschiedenen systemen (theoretisch) verschieden sein - aber das ist ja jeder aufruf(außer 2 buchstaben oder 2 ziffern).bb
-
Hallo,
erstmal sorry für die späte Rückmeldung und vielen Dank für die Mühe...das Programm läuft bei mir leider aber noch nicht ganz fehlerfrei...
"Übung" wird z.b. vor "Antwort" einsortiert, obwohl es eigentlich in die Nähe von "u" und nicht vor "a" gehört...laut Wikipedia gibt es bezüglich der Frage, wo genau es bei "u" einsortiert wird zwar auch noch diverse Möglichkeiten, aber ein "ü" vor dem "a" ist für mein Verständnis nicht richtig...die Umlaute sind demnach scheinbar trotz locale noch ein Problem...
In Java gibt es dafür wohl so etwas wie eine "Sortier-Stärke":
http://www.i-coding.de/www/de/java/liste/sortieren.html
Ich hatte gehofft, dass es auch für C++ eine Lösung wie "Collator.SECONDARY" gibt, mit der man Umlaute und eventuell sogar Akzente wie "á" (gehört für mein Verständnis in die Nähe von "a") wie z.B in Excel korrekt sortieren kann...
-
Mr. Speck schrieb:
"Übung" wird z.b. vor "Antwort" einsortiert, obwohl es eigentlich in die Nähe von "u" und nicht vor "a" gehört...laut Wikipedia gibt es bezüglich der Frage, wo genau es bei "u" einsortiert wird zwar auch noch diverse Möglichkeiten, aber ein "ü" vor dem "a" ist für mein Verständnis nicht richtig...die Umlaute sind demnach scheinbar trotz locale noch ein Problem...
Japp, das ist klar, dass dies passiert - liegt einfach daran, dass ü im ascii-code eben nicht zwischen u und v liegt sondern (kA, wo vrmtl aber) vor dem a^^
Gleiches gilt für ß...
und das wird gleich noch mal ganz wo anders eingeordnet, wenn die buchstaben in caps verglichen werden (weil es nen extra zeichen für toupper('ß') gibt: 'SS' - wird zwar sicherlich nicht so geschrieben, aber so würde es aussehen und es ist nur ein zeichen^^)
Würde aber relativ zu fixen gehen:
typename TCharTraits = std::char_traits<TChar>, //uses TCharTraits::lt(TChar, TChar)
steht in meinem code ja.wenn du also einfach so was baust:
struct my_char_trait_lt { static bool lt(char lhs, char rhs) { lhs = get_char_next_to(lhs); rhs = get_char_next_to(rhs); return lhs < rhs; } private: static char get_char_next_to(char value) { switch (value) { case 'ü': return 'u'; case 'Ü': return 'U'; /*...*/ } return value; } };
nä. Problem(?): Üasd wird nicht bei Ueasd eingeordnet sondern bei Uasd
und ich weiß nicht, was passiert, wenn nen franzose/russe/... das compiliert
und ich weiß nicht, wer sich hinsetzen will und das für alle sonderzeichen machen will xDbb
-
Vielleicht ein wenig aufwändig, aber so sollte es funktionieren (ungetestet)
int get_char_val(char a) { switch(a) { case 'a': case 'A': case 'à': case 'á': case 'À': case 'Á': case 'ä': case 'Ä': return 1; break; case 'b': case 'B': return 2; break; case 'c': case 'C': return 3; break; //usw default: return -1; break; } } //string compare verhält sich jetzt wie der < Operator bool string_compare(const std::string& a, const std::string &b) { for(std::string::const_iterator a_it=a.begin(), b_it=b.begin(), a_end=b.end(), b_end=b.end(); a_it != a_end && b_it != b_end; ++a_it, ++b_it) { int a_tmp = get_char_val(*a_it), b_tmp = get_char_val(*b_it); if( a_tmp < b_tmp ) return true; if( ga_tmp > b_tmp ) return false; } //wenn die strings bis hierhin gleich sind return a.size() < b.size(); } int main() { std::vector<std::string> MeineStrings; sort(MeineStrings.begin(), MeineStrings.end(), string_compare); //sortieren }
-
soweit ich das beim überfliegen der Doku gesehen hatte haben die locales mit collate eine möglichkeit, chars anders zu vergleichen als nach ihrem ASCII-Wert. Ich würde dann erwarten dass umlaute bei einer derutschen locale entsprechend einsortiert werden. Vielleicht hab ich da die Möglichkeiten der locales aber auch überschätzt.
-
pumuckl schrieb:
soweit ich das beim überfliegen der Doku gesehen hatte haben die locales mit collate eine möglichkeit, chars anders zu vergleichen als nach ihrem ASCII-Wert. Ich würde dann erwarten dass umlaute bei einer derutschen locale entsprechend einsortiert werden. Vielleicht hab ich da die Möglichkeiten der locales aber auch überschätzt.
hatte ich anders verstanden:
<a href= schrieb:
http://www.cplusplus.com/reference/std/locale/collate/compare/">
For the standard specialization of char and wchar_t the function returns the same as lexicographical_compare, which performs a character code (ASCII) comparison between the individual characters of both sequences, returning an alphabetical ordering for alphabetical strings.bb
-
Übrigens, weil ich es gerad zufällig gesehen habe...
In den meinVZ-Seiten (was imho in Java geschrieben ist), wird auch so sortiert, wie ich das gepostet hab^^ (..., a..z, ..., ü_ä_ö)bb
-
pumuckl schrieb:
soweit ich das beim überfliegen der Doku gesehen hatte haben die locales mit collate eine möglichkeit, chars anders zu vergleichen als nach ihrem ASCII-Wert. Ich würde dann erwarten dass umlaute bei einer derutschen locale entsprechend einsortiert werden. Vielleicht hab ich da die Möglichkeiten der locales aber auch überschätzt.
Ich denke, das std::collate<> genau die richtige Baustelle ist. Der Trick besteht darin, jeden zu vergleichenden String in eine 'standardisierte Darstellung' zu konvertieren (etwa Ä->AE und ß->SS) und dann diesen entstanden String zu vergleichen. Ersteres passiert in der Methode collate<>::do_transform.
Und so sollte es gehen:
#include <algorithm> #include <locale> #include <iostream> #include <iterator> #include <numeric> #include <string> #include <vector> class Compare : public std::collate< char > { protected: virtual string_type do_transform( const char* low, const char* high ) const { const std::locale loc; string_type res; for( ; low != high; ++low ) { char c = std::toupper( *low, loc ); switch( c ) { case 'Ä': res.append( "AE" ); break; case 'Ö': res.append( "OE" ); break; case 'Ü': res.append( "UE" ); break; case 'ß': res.append( "SS" ); break; default: res.append( 1, c ); } } return res; } virtual int do_compare( const char* low1, const char* high1, const char* low2, const char* high2 ) const { const string_type s1 = do_transform( low1, high1 ); const string_type s2 = do_transform( low2, high2 ); if( s1 == s2 ) return 0; return s1 < s2? -1: 1; } virtual long do_hash( const char* low, const char* high ) const { const string_type tmp = do_transform( low, high ); return std::accumulate( tmp.begin(), tmp.end(), long(0) ); } }; struct Foo // Mr. Specks struct, die einen string enthält { Foo( const std::string& name = "<noname>" ) : m_name( name ) {} bool operator<( const Foo& b ) const { return m_locale( m_name, b.m_name ); } friend std::ostream& operator<<( std::ostream& out, const Foo& foo ) { return out << foo.m_name; } std::string m_name; static std::locale m_locale; }; std::locale Foo::m_locale = std::locale( std::locale(), new Compare ); int main() { using namespace std; vector< Foo > v; v.push_back( Foo( "Übung" ) ); v.push_back( Foo( "August" ) ); v.push_back( Foo( "Straße" ) ); v.push_back( Foo( "straße" ) ); v.push_back( Foo( "augusT" ) ); v.push_back( Foo( "ulm" ) ); sort( v.begin(), v.end() ); copy( v.begin(), v.end(), ostream_iterator< Foo >( cout, "\n" ) ); cout << endl; return 0; }
Natürlich lassen sich in do_transform noch weitere Zeichen - wie etwa 'á' - hinzufügen.
Gruß
Werner
-
1. würdest du es auch so machen, werner? das muss doch iwie toller gehen, als alles einzeln anzugeben?!
const std::locale loc; //standard locale string_type res; for( ; low != high; ++low ) { char c = std::toupper( *low, loc ); //BÄM! bei allen (nationalen) sonderzeichen switch( c ) { case 'Ä': res.append( "AE" ); break; case 'Ö': res.append( "OE" ); break; case 'Ü': res.append( "UE" ); break; case 'ß': //ich glaube nicht, dass das richtig ist... res.append( "SS" ); break;
und vor allem: was passiert denn nun, wenn ich in meinem Windows keine deutsche Sprache habe sondern nur Französisch oder so?
was passiert dann beispielsweise beicase 'Ä'
?
ich meine - bei nem deutschen steht im compilat dann iwann case 123 -> "AE" oder so und bei nem franzosen ist #123 aber vll nen ô oder so...
das kann doch nicht wirklich der richtige ansatz sein!?bb
-
unskilled schrieb:
1. würdest du es auch so machen, werner? das muss doch iwie toller gehen, als alles einzeln anzugeben?!
Ja - nach meinem Kenntnisstand ist das der Weg. Du kannst natürlich auch noch Tabellen aufstellen, so dass es vielleicht etwas eleganter aussieht; aber am Prinzip ändert das nichts.
unskilled schrieb:
const std::locale loc; //standard locale string_type res; for( ; low != high; ++low ) { char c = std::toupper( *low, loc ); //BÄM! bei allen (nationalen) sonderzeichen
Gut erkannt. Natürlich gehe ich davon aus, dass die hier verwendete Locale zum Rest des Codes passt.
Besser wäre hier wahrscheinlich:const std::locale loc("german"); //deutsche locale
unskilled schrieb:
und vor allem: was passiert denn nun, wenn ich in meinem Windows keine deutsche Sprache habe sondern nur Französisch oder so?
was passiert dann beispielsweise beicase 'Ä'
?
ich meine - bei nem deutschen steht im compilat dann iwann case 123 -> "AE" oder so und bei nem franzosen ist #123 aber vll nen ô oder so...
das kann doch nicht wirklich der richtige ansatz sein!?Für Französisch muss man ein französisches Compare schreiben. Das Sortieren hängt von der Sprache und vor allen vom verwendeten Zeichensatz ab.
Gruß
Werner
-
Werner Salomon schrieb:
Für Französisch muss man ein französisches Compare schreiben. Das Sortieren hängt von der Sprache und vor allen vom verwendeten Zeichensatz ab.
Hmm... Das stinkt ja...
Déjà-vu und über müssen doch mit dem gleichen C++ Code verglichen werden können...
Wird man wohl deine Lösung zusammen mit wide-strings nutzen müssen...bb
-
unskilled schrieb:
Déjà-vu und über müssen doch mit dem gleichen C++ Code verglichen werden können...
ja - mit einem Deutsch-französischen Compare
unskilled schrieb:
Wird man wohl deine Lösung zusammen mit wide-strings nutzen müssen...
Genauso ..
Das Problem beim Sortieren ist doch zum einen das Groß-/Klein-Schreiben. Da kommt es darauf an, wie es der User gerne hätte und das zweite sind die Besonderheiten bei Zeichen wie ä bzw. á. Beide haben Zeichencodes die jenseits von a-z bzw. A-Z liegen, aber nach den nationalen Sortierungsgewohnheiten jeweils in die Nähe des a gehören. Und das muss man halt gesondert behandeln.
Gruß
Werner
-
Mit Windows-Funktionen klappt das übrigens ganz prima:
#include <functional> #include <algorithm> #include <string> extern "C" { #include <windows.h> } struct Cmp: public std::binary_function<std::string &, std::string &, bool> { bool operator()(const std::string &lhs, const std::string &rhs) const { int rc = CompareString(LOCALE_USER_DEFAULT, 0, lhs.c_str(), -1, rhs.c_str(), -1); return rc == CSTR_LESS_THAN; } }; int main(int argc, char *argv[]) { std::vector<std::string> strings; strings.push_back("Déjà-vu"); strings.push_back("Bäche"); strings.push_back("Ärger"); strings.push_back("Bass"); strings.push_back("Strass"); strings.push_back("Bässe"); strings.push_back("Degen"); strings.push_back("Straße"); strings.push_back("Ähre"); strings.push_back("Destruktor"); strings.push_back("Bär"); strings.push_back("déjà-vu"); strings.push_back("bäche"); strings.push_back("ärger"); strings.push_back("bass"); strings.push_back("strass"); strings.push_back("bässe"); strings.push_back("degen"); strings.push_back("straße"); strings.push_back("ähre"); strings.push_back("destruktor"); strings.push_back("bär"); std::sort(strings.begin(), strings.end(), Cmp()); for(std::vector<std::string>::iterator iter = strings.begin(); iter != strings.end(); ++iter) { CharToOem(iter->c_str(), const_cast<char *>(iter->c_str())); std::cout << *iter << std::endl; } return 0; }
Gibt folgendes aus:
ähre Ähre ärger Ärger bäche Bäche bär Bär bass Bass bässe Bässe degen Degen déjà-vu Déjà-vu destruktor Destruktor strass Strass straße Straße
Das CharToOem() sorgt nur dafür, dass die Shell es auch anständig anzeigt.
Ich muss sagen, dass ich, statt entsprechende Compare-Tabellen für alle möglichen Sprachen zu schreiben, lieber Funktoren für alle möglichen Betriebssysteme schreiben würde.
Stefan.
-
jopp - da hast du definitiv recht ^^
aber das kann ja auch keiner wissen, der die winapi nicht auswendig kennt ;o)btw:
extern "C" { #include <windows.h> }
wieso tust du das? imho ist das intern eh iwo(dort wo es halt nötig ist^^) mit
#ifdef __cplusplus extern "C" { #endif
geregelt - oder lieg ich da falsch?! (viel mehr als das es sie gibt weiß ich ja über die winapi auch nicht ;o))bb
-
unskilled schrieb:
jopp - da hast du definitiv recht ^^
aber das kann ja auch keiner wissen, der die winapi nicht auswendig kennt ;o)Ich kenn's auch nur, weil ich mich hundert Jahre lang mit dem C-Locale und Verwandten herumgeschlagen habe. Und irgendwann aufgegeben
Beispielsweise habe ich diese Sortiererei mit Standard-C++ auch versucht und bin gescheitert. Oder habe versucht herauszufinden, wie man mit Standard-C++ den Komma-Separator ermitteln kann. Dazu hatte ich hie auch mal eine Anfrage laufen und keiner wusste es.
Das Windows-API ist in dieser Hinsicht sehr viel einfacher und übersichtlicher, also verwende ich jetzt immer die.
unskilled schrieb:
btw:
extern "C" { #include <windows.h> }
wieso tust du das? imho ist das intern eh iwo(dort wo es halt nötig ist^^) mit
#ifdef __cplusplus extern "C" { #endif
geregelt - oder lieg ich da falsch?! (viel mehr als das es sie gibt weiß ich ja über die winapi auch nicht ;o))bb
Hab gerade mal nachgeschaut - allerdings nur flüchtig: In windows.h des GCC (MinGW) konnte ich sowas nicht finden, ebenfals nicht im Header des Visual Studio (2005). Keine Ahnung, ob es auch ohne extern "C" funktionieren würde. Ich mache das schon lange so - aus reiner Gewohnheit
Stefan.
-
Habe mir nicht alle Posts durchgelesen, aber vielleicht ist ja hier was dabei.