Sortierung verbessern/beschleuningen?

  • C++

Es gibt 14 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    Sortierung verbessern/beschleuningen?

    Hey,

    ich habe einen vector<string> mit CD-Namen, diesen sortiere ich alphabetisch und wenn eine Nummer am Ende ist, wird das auch numerisch sortiert. Dauert mir aber ein wenig lange, schon auf Windows, es soll später auf einem Raspi laufen, da wirds noch länger dauern, schon allein weil von Micro-SD gelesen wird.

    Beispiel Ergebnis der Sortierung
    ACD_1
    aCD_2
    ACD_2
    ACD_12
    BCD_1
    BCD_12
    bCD_2
    usw. ....

    So sortiere ich das:

    C-Quellcode

    1. bool IsCharLess(char c1, char c2)
    2. {
    3. return tolower(static_cast<unsigned char>(c1)) < tolower(static_cast<unsigned char>(c2));
    4. }
    5. bool Comparer(const string& s1, const string& s2)
    6. {
    7. smatch m1, m2;
    8. regex r("\\d+$");
    9. if (regex_search(s1, m1, r) && regex_search(s2, m2, r))
    10. {
    11. string a(m1.prefix());
    12. string b(m2.prefix());
    13. if (a.compare(b) == 0)
    14. {
    15. return stoi(m1.str()) < stoi(m2.str());
    16. }
    17. }
    18. return lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), IsCharLess);
    19. }


    C-Quellcode

    1. vector<string> albums = vector<string>();
    2. //albums füllen;
    3. sort(albums.begin(), albums.end(), Comparer);


    Ohne Sortierung dauert der Vorgang(Daten aus dem Dateisystem hohlen) < 1 Sekunde, mit sortierung mehr als 10, die Sortierung ist also der Flaschenhals. Kann man die Sortierung irgendwie schneller hinkriegen?

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Takafusa“ ()

    @Takafusa Ich denke mal, lexicographical_compare() dauert sehr lange.
    Den Algorithmus würde ich in VB.NET oder C# optimieren, da geht das Optimieren schneller.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Leider kann ich nicht so einfach auf Net.Core umsteigen. Ich habe mir mit SDL2 eine Control-Biliothek in C++ erschaffen(bzw. bin noch dran, werde irgendwie nie fertig) läuft wunderbar auf Windows und Linux, das alles zu portieren würde mir zu lange dauern, arbeite schon sicher 1 jahr oder mehr an meinem "Kleinen-Framework".

    Jemand hat mir empfohlen sqlite zu implementieren um so nicht immer neu alles einzulesen(beim Programmstart), die Daten dann sortiert aus der DB hohlen. Wollte ich zwar nicht, weil ich noch keine Ahnung hab von DBs mit C++, aber es scheint es wäre angebracht mich mal mit sqlite unter C++ zu beschäftigen, mit Java habe ich vor langer Zeit mal damit gearbeitet. Da war Eclipse noch die Android IDE.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Takafusa“ ()

    @Takafusa Du sollst nicht umsteigen, sondern den Algorithmus in einer anderen Sprache entwickeln und optimieren.
    Danach überträgst Du den Algorithmus nach C++.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Achso hast du das gemeint. Ich habe noch ein paar Tests gemacht, Regex ist hier der Falschenhals, hab das auskommentiert so das der returnwert von lexicographical_compare direkt zurückgeht. Kaum ein merkbarer Unterschied mit und ohne Sortierung.

    Takafusa schrieb:

    Regex ist hier der Falschenhals
    Oha.
    Lässt sich das evtl. umgehen?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Ich überlege gerade wie ich das umbaue. Ich denke ich werde die beiden strings von hinten nach vorne parsen, sollte am Ende beider strings eine Nummer sein und beide strings ohne diese Nummern identisch sein, beide Zahlen durch stoi jagen und vergleichen welche Nummer kleiner ist. Ich probier das mal.
    Also die Strings sind Ordnernamen von Musik-CD-Ordnern, bestehend aus Artist und Albumname getrennt mit einem - . (siehe Anhang)

    Edit: @RodFromGermany
    Ich hab jetzt einen Prototypen fertig, geht deutlich flotter. Noch verschönern dann geht das wohl, bin aber für andere Vorschläge oder verbesserungen offen.
    Dirty Prototype:

    C-Quellcode

    1. bool IsCharLess(char c1, char c2)
    2. {
    3. return tolower(static_cast<unsigned char>(c1)) < tolower(static_cast<unsigned char>(c2));
    4. }
    5. bool Comparer(const string& s1, const string& s2)
    6. {
    7. string str1, str2;
    8. bool s1EndsWithNumber = false;
    9. bool s2EndsWithNumber = false;
    10. for (int i = s1.length() - 1; i > -1; i--)
    11. {
    12. const unsigned char c = s1[i];
    13. if (isdigit(c))
    14. {
    15. str1 += c;
    16. }
    17. else
    18. {
    19. break;
    20. }
    21. }
    22. if (str1.length() > 0)
    23. {
    24. s1EndsWithNumber = true;
    25. reverse(str1.begin(), str1.end());
    26. }
    27. for (int i = s2.length() - 1; i > -1; i--)
    28. {
    29. const unsigned char c = s2[i];
    30. if (isdigit(c))
    31. {
    32. str2 += c;
    33. }
    34. else
    35. {
    36. break;
    37. }
    38. }
    39. if (str2.length() > 0)
    40. {
    41. s2EndsWithNumber = true;
    42. reverse(str2.begin(), str2.end());
    43. }
    44. if (s1EndsWithNumber && s2EndsWithNumber)
    45. {
    46. string s1a = s1.substr(0, s1.length() - str1.length());
    47. string s2a = s2.substr(0, s2.length() - str2.length());
    48. if (s1a.compare(s2a) == 0)
    49. {
    50. return stoi(str1) < stoi(str2);
    51. }
    52. }
    53. return lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), IsCharLess);
    54. }

    Bilder
    • Unbenannt.jpg

      117,39 kB, 175×868, 73 mal angesehen

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „Takafusa“ ()

    Takafusa schrieb:

    C-Quellcode

    1. if (isdigit(c))
    Drehe die if-Logik um und nimm das else raus.
    Wenn die Namen alle so aussehen, arbeite mit Split und nimm dann die Teile unter die Lupe.
    Somit wäre Regex überflüssig.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Es kann aber muss keine Nummer am Ende sein. Der numerische vergleich soll nur wenn Nummern am Ende sind stattfinden, daher teste ich ob die strings ohne diese Nummern also "Artist - Albenname [entfernte nummer]" gleich sind.

    Bedingungen angepasst, ist ein wenig besser jetzt. Alles viel schneller, die Sortierung stimmt auch.

    C-Quellcode

    1. bool Comparer(const string& s1, const string& s2)
    2. {
    3. string str1, str2;
    4. unsigned char c;
    5. for (int i = s1.length() - 1; i > -1; i--)
    6. {
    7. c = s1[i];
    8. if (!isdigit(c))
    9. {
    10. break;
    11. }
    12. str1 += c;
    13. }
    14. for (int i = s2.length() - 1; i > -1; i--)
    15. {
    16. c = s2[i];
    17. if (!isdigit(c))
    18. {
    19. break;
    20. }
    21. str2 += c;
    22. }
    23. if (str1.length() > 0 && str2.length() > 0)
    24. {
    25. reverse(str2.begin(), str2.end());
    26. reverse(str1.begin(), str1.end());
    27. string s1a = s1.substr(0, s1.length() - str1.length());
    28. string s2a = s2.substr(0, s2.length() - str2.length());
    29. if (s1a.compare(s2a) == 0)
    30. {
    31. return stoi(str1) < stoi(str2);
    32. }
    33. }
    34. return lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), IsCharLess);
    35. }


    @RodFromGermany Danke für den Stups in die richtige Richtung, wäre heute sicher nicht auf die Idee gekommen regex rauszuwerfen. Nach deinem Tipp, einen Algo selbst zu erstellen, kam ich erstmal auf die Idee den Regex-Teil auszukommentieren, sonst hätte ich heute nicht mehr geschnallt das dies der Flaschenhals war.

    Konnte mich also wieder mal davor drücken, DBs mit C++ in Angriff zu nehmen. :D
    Bilder
    • Unbenannt.png

      5,49 kB, 483×325, 69 mal angesehen

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „Takafusa“ ()

    Also so wie ich es verstanden habe ist alles was du möchtest es lexikalisch zu sortieren?

    C-Quellcode

    1. bool compare(const std::string &left, const std::string &right)
    2. {
    3. using CharPtr = std::string::const_pointer;
    4. CharPtr lch = left .data();
    5. CharPtr rch = right.data();
    6. for (;;)
    7. {
    8. if (*lch == '\0' || *rch == '\0')
    9. {
    10. return *lch < *rch;
    11. }
    12. if (*lch != *rch)
    13. {
    14. return *lch < *rch;
    15. }
    16. (void) ++lch;
    17. (void) ++rch;
    18. }
    19. }


    Das Glück ist das auch nummerische Werte in der ASCII-Tabelle enthalten sind, daher kann man direkt nach diesem Wert gehen.
    Ich hoffe mal ich hab da jetzt keinen Schnitzer reingeworfen, ich hab das nämlich am Smartphone geschrieben und getestet. (da geht schnell mal einiges Panflöten)

    Hier is' halt so dass das ASCII-compliant ist, wenn andere Enkodierungen verwendet werden, ist das natürlich ein wenig anders!

    Es gibt auch noch eine STL variante, die so trivial wie auch effizient ist:

    C-Quellcode

    1. std::sort(vec.begin(), vec.end(), [](const std::string &str1, const std::string &str2) { return str1 < str2; });


    Das Glück hier ist das std::string einen größer-kleiner operator anbietet der dies für dich übernimmt.
    Ich würde warscheinlich diesen Wählen, da meiner zu ungetestet ist.

    Und hier auch noch ein Benchmark zwischen deinem, meinem und dem STL-Algorithmus:
    quick-bench.com/q/auKpfOpUnDU0qjaaAuMuOs_XHMQ

    Edit: hab erst gar nicht gemerkt dass du nicht nur lexikalisch sondern auch numerisch sortierst, das geht mit meinem und dem STL algorithmus natürlich nicht!
    Das hier hat aber 4 Tests bestanden:

    C-Quellcode

    1. inline constexpr char numZero = '0';
    2. inline constexpr char numNine = '9';
    3. bool isNumeric(char val)
    4. {
    5. return val >= numZero && val <= numNine;
    6. }
    7. int getNumVal(char val)
    8. {
    9. return val - numZero;
    10. }
    11. bool compare(const std::string &left, const std::string &right)
    12. {
    13. using CharPtr = std::string::const_pointer;
    14. CharPtr lch = left .data();
    15. CharPtr rch = right.data();
    16. for (;;)
    17. {
    18. if (*lch == '\0' || *rch == '\0')
    19. {
    20. return *lch < *rch;
    21. }
    22. if (isNumeric(*lch) && isNumeric(*rch))
    23. {
    24. int number_1 = 0;
    25. int number_2 = 0;
    26. if (*lch == '0' && *rch != '0')
    27. {
    28. while (*lch == '0')
    29. {
    30. if (*(++lch) == '\0')
    31. {
    32. return true;
    33. }
    34. }
    35. }
    36. else if (*lch != '0' && *rch == '0')
    37. {
    38. while (*rch == '0')
    39. {
    40. if (*(++rch) == '\0')
    41. {
    42. return false;
    43. }
    44. }
    45. }
    46. for (;;)
    47. {
    48. if (isNumeric(*lch) != isNumeric(*rch))
    49. {
    50. return !isNumeric(*lch);
    51. }
    52. if (!isNumeric(*lch) && !isNumeric(*rch))
    53. {
    54. if (number_1 != number_2)
    55. {
    56. return (number_1 < number_2);
    57. }
    58. break;
    59. }
    60. number_1 = (number_1 * 10 + getNumVal(*lch));
    61. number_2 = (number_2 * 10 + getNumVal(*rch));
    62. (void) ++lch;
    63. (void) ++rch;
    64. }
    65. continue;
    66. }
    67. if (*lch != *rch)
    68. {
    69. return *lch < *rch;
    70. }
    71. (void) ++lch;
    72. (void) ++rch;
    73. }
    74. }


    Neuer benchmark:
    quick-bench.com/q/yd43QDdR2OmYFFJBiVgIkTZBLKE
    Bilder
    • Screenshot_20210618_130107.png

      14,27 kB, 909×441, 70 mal angesehen
    ----------------------------------------------------------------------------------------------------------------------

    Hier könnte meine Signatur stehen, aber die ist mir abfußen gekommen.

    ----------------------------------------------------------------------------------------------------------------------

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „Elanda“ ()

    Hey Elanda,

    das sieht soweit sehr überzeugend aus. Ich werde heute Abend mal schauen ob die Sortierung passt, gegebenenfalls anpassen und übernehmen. Ich denke das lexicographical_compare bei mir noch bremst, merkt man aber kaum, ein Paar Tausend Ordner(ohne inhalte) sind in < 1.5 - 2 Sekunden eingelesen. Die Inhalte werden beim erst selektieren eines Albums in der GUI eingelesen.

    Ich danke Dir :)

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Takafusa“ ()

    @Elanda Der Code ist wirklich schnell, die Sortierung passt nicht ganz. Weil kleingeschrieben Char erst nach dem großen Z in der Ausgabe sind. Werde einfach wenn char >= 97 && char <= 122, 32 abziehen.


    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Takafusa“ ()

    Takafusa schrieb:

    Werde einfach wenn char >= 97 && char <= 122, 32 abziehen.
    Da musst Du nix testen, sondern Du machst ein bitweises Not: & ~32 und feddich.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!