Richtige Suchfunktion [gelöst]



  • Tag zusammen,

    Ich suche nach einer besseren Art zu suchen, aber finde leider keine entsprechende Funktion. Folgender Code:

     #include <iostream>
     #include <string>
                    
    int main (){
        std::string category [4] = {"category (value1/value2/value3)", "category2 (value2/value1/value3)", "category3 (value3/value2/value1)", "category4 (value1/value2/value3)"};
        std::string values [3] = {"value1", "value2", "value3"};
        int value [3] = {1,2,3};
        int answer;
                        
                        
        for (int i=0; i<4; i++){
            std::cout << i << ": " << category [i] <<std::endl <<std::endl;
        }
        std::cout <<"Eingabe: ";
        std::cin >> answer;
        std::cout << std::endl;
        
        for (int i=0; i<4;i++){
            if (answer == i){
                std::cout << category [i] << " = (";
                
                for (int x=0; x<3; x++){
                    if (category[i].find(values[x]) != std::string::npos){
                        std::cout << value[x];
                        if (x!=2){
                            std::cout <<"/";
                        }
                    }
                }
                std::cout << ")";
            }
        }  
        return 0;
    }   
    
    

    Es sollen erst alle Kategorien ausgegeben werden und bei entsprechender Eingabe (0 = category; 1=category2 ....etc.) die passende Kategorie + dazugehörige Werte (int value) ausgegeben werden. "value1" enstpricht dabei value [0].

    Wenn ich nun also mit std::find suche, werden die Werte "category" und "category4" richtig zugeordnet, da die Reihenfolge der Werte chronologisch ist.

    Ändere ich allerdings die Reihenfolge der Werte im String (Beispiel "category2" und "category3") werden die Werte des Integers natürlich falsch zugwiesen, da immer chronologisch gemäß des strings values gesucht wird.

    Ich brauche also eine Funktion (oder erweiternde Paramater für std::find), die innerhalb des category string arrays innerhalb der Klammer die erste Position gesondert von der zweiten und der dritten untersucht.

    Hoffe ihr wisst was ich meine, bin selbst ziemlich verwirrt...
    Am besten ihr compiled den Code mal, dann wird es wahrscheinlich ein wenig klarer.

    Gruß,



  • Das hört sich doch ganz danach an, dass Sie so etwas wie einen Parser suchen. Man kann dafür etwa die Boost Spirit Bibliothek verwenden oder ein geeignetes externes Werkzeug. Der Klassiker wäre hier lex&yacc.



  • Vielen Dank für Ihre Antwort! Der Verweis auf Parser bzw. auf lex&yacc scheint sehr vielversprechend zu sein und quasi genau das, wonach ich suche, allerdings würde mich interessieren, ob es nicht einfachere Möglichkeiten in diesem konkreten Anwendungsfall gibt.



  • @Richard_Wunsch :
    Ich hoffe, du hast dir gut gemerkt, wann du was getippt hast, damit du "chronologisch" sortieren kannst 😉

    Jetzt mal ohne Fehlerabfangen und sowas, kannst du grob sowas machen:

    #include <iostream>
    #include <map>
    #include <string>
    #include <algorithm>
    
    int main() {
        std::string cat =  "category2 (value2/value1/value3)";
        std::string values [3] = {"value1", "value2", "value3"};
        int value [3] = {1,2,3};    
        
        std::map<size_t, int> posToValue;
        for(size_t i=0; i< 3;++i)
        {
        	auto pos = cat.find(values[i]);
            posToValue[pos] = value[i];
        }   
        
        for(auto& v : posToValue)
        {
        	std::cout << v.second;  // 213
        }
        return 0;
    }
    

    Edit: Gerade mal im Duden nach "chronologisch" geschaut. Ist doch für mehr synonym, als mir bewusst war. Der Scherz am Anfang ist also in die Hose gegangen 😉



  • ... aber was passiert bei
    cat = "Categorvalue123ies (value2/value1)";

    Also soll das erste value1 auch gefunden werden oder nicht? Wenn nein, dann solltest du vermutlich umgekehrt vorgehen und die Zielstrings aus cat herausparsen und dann jeweils in einer map von "values" auf "value"* nachschauen. Außerdem auch in diesem Beispiel: darf es ein valueX mehrfach geben?

    * furchtbare Namen. Values ist ein String-Array, value ist aber nicht etwa ein String aus diesem Array, sondern ein int-Array. Mit solchen Namen wirst du niemals glücklich werden.



  • Danke für eure Antworten! Ich werde anscheinend nicht um eine map herumkommen, dachte es gibt vielleicht einen einfacheren Weg. Merken kann ich mir ganz gut was ich getippt habe, weil das in einer .txt datei gespeichert wird.*

    Die Namen sind echt furchtbar, das ist mir bewusst. Wollte nur schnell einen beispielhaften Code schreiben und habe gehofft, dass eure geübten Augen die schlampigen Namen wohlwollend ignorieren.

    Der eigentliche category array kann bis zu 100 Einträge lang sein, weshalb ich deine Lösung @Jockelx jetzt noch irgendwie in die anfänglichen Schleifen integrieren muss....keine Ahnung ob mir das bis Weihnachten noch gelingt.

    @wob: Die Kategorien haben fix immer drei Werte - also sind immer so aufgebaut "Kategorie (Wert/Wert/Wert)". Aber ja, ein valueX kann auch mehrfach vorkommen: "Kategorie (value1/value2/value2)".

    Edit: *Oh, das sollte Teil deines Witzes sein...



  • @Richard_Wunsch sagte in Richtige Suchfunktion:

    Die Kategorien haben fix immer drei Werte - also sind immer so aufgebaut "Kategorie (Wert/Wert/Wert)". Aber ja, ein valueX kann auch mehrfach vorkommen: "Kategorie (value1/value2/value2)".

    Dann such doch nach der öffnenden und schließenden Klammer. Darin splittest du den String an /. Für jeden gesplitteten String schaust du in einer Map nach, wie der Wert ist.

    map<string, int> m = {{"value1", 1}, {"value2", 2}, {"value3", 3}};



  • @wob sagte in Richtige Suchfunktion:

    Dann such doch nach der öffnenden und schließenden Klammer. Darin splittest du den String an /.

    Genau daran habe ich auch schon gedacht. Lass mich kurz mein Hirn zermartern, wie ich das schaffe. Ich versuch bis heute Abend eine entsprechende Lösung zu posten.



  • @Richard_Wunsch sagte in Richtige Suchfunktion:

    Lass mich kurz mein Hirn zermartern, wie ich das schaffe.

    boost::split?
    absl::StrSplit?
    ...

    Oder von Hand jeweils das / suchen?



  • @wob Ist als blutiger Anfänger nicht so einfach, wie man sich das vielleicht vorstellen mag.

    Dachte nach kurzer Recherche eher an std::substr mit dem "(" als pos. Sieht auf den ersten Blick nachvollziehbarer aus als deine Verweise.

    Von Hand das "/" suchen, splittet mir aber noch nicht den String, oder wie meintest du das?

    Edit:

     #include <iostream>
     #include <string>
                    
    int main (){
        std::string category = "category (value1/value2/value3)";
        std::string firstValue = category.substr((category.find("("))+1, 6); 
        std::string secondValue = category.substr((category.find("/"))+1, 6);
        std::string thirdValue = category.substr((category.find(")"))-6, 6);
        return 0;
    }     
    

    Zu unsauber?



  • Geschafft! Sieht so aus, als ob ich einen funktionierenden Code zusammengebastelt hätte:

    #include <iostream>
     #include <string>
     #include <map>
                    
    int main (){
        
        std::map<std::string, int> m = {{"value1", 1}, {"value2", 2}, {"value3", 3}};
        std::string category [4] = {"category (value1/value2/value3)", "category2 (value2/value1/value3)", "category3 (value3/value2/value1)", "category4 (value1/value2/value3)"};
        std::string firstValueName[4] = {};
        std::string secondValueName[4]= {};
        std::string thirdValueName[4] = {};
        int answer;
        
        for (int i=0; i<4; i++){
            firstValueName[i] = category[i].substr((category[i].find("("))+1, 6); 
            secondValueName[i] = category[i].substr((category[i].find("/"))+1, 6);
            thirdValueName[i] = category[i].substr((category[i].find(")"))-6, 6);
        }
                        
        for (int i=0; i<4; i++){
            std::cout << i << ": " << category [i] <<std::endl <<std::endl;
        }
        std::cout <<"Eingabe: ";
        std::cin >> answer;
        std::cout << std::endl;
        
        for (int i=0; i<4;i++){
            if (answer == i){
                std::cout << category [i] << " = ("
                          << m.find(firstValueName[i])->second << "/"
                          << m.find(secondValueName[i])->second << "/"
                          << m.find(thirdValueName[i])->second << ")";
            }
        }
        return 0;
    }      
    

    Ich implementiere ihn später mal in die eigentliche Anwendung und gucke mal, wie er sich so schlägt. Danke noch einmal an alle Beitragende! Ohne euch hätte ich es sicher nicht geschafft 🙂

    Ansonsten...immer her mit eurer Kritik!



    • Kodiere die Array-Längen nicht fest in deinem Code!
    • Kodiere die 6 nicht fest, sondern errechne sie
    • firstValueName / secondValueName / thirdValueName würde ich nicht als Array vorberechnen
    • m.find(firstValueName[i])->second ist eine komplizierte Schreibweise von m.at(firstValueName[i]) (also nicht 100% identisch, verhält sich anders bei nicht gefundenen Schlüsseln) -> du solltest wohl doch m.find benutzen, aber das Ergebnis erstmal mit m.end() vergleichen, bevor du auf ->second zugreifst. Mit m.at kommt wenigstens dann wenigstens ein definierter Fehler, wäre einem find ohne check also vorzuziehen.
    • Benutze kurze Funktionen - nicht alles in einem langen main()


  • @wob Danke für deine Hilfe ❤



  • @wob ich muss doch noch mal nachhaken: Inwiefern die Array-Längen nicht fest kodieren? Im genannten Beispiel werden die Daten doch konstant initalisiert - wie sollte ich da die Array-Länge ersetzen?

    Und mit deinem 3. Punkt wolltest du darauf hinaus, richtig?

        for (int i=0; i<4;i++){
            if (answer == i){
                std::cout << category [i] << " = ("
                          << m.find(category[i].substr((category[i].find("("))+1, 6))->second << "/"
                          << m.find(category[i].substr((category[i].find("/"))+1, 6))->second << "/"
                          << m.find(category[i].substr((category[i].find(")"))-6, 6))->second << ")";
            }
        }
    


  • @Richard_Wunsch sagte in Richtige Suchfunktion [gelöst]:

    Inwiefern die Array-Längen nicht fest kodieren?

    Du hast x-Mal in deinem Programm "4" stehen. Was ist, wenn du auf einmal 3 oder 5 Elemente haben möchtest? Welche Vieren im Programm musst du ändern? Daher: nimm Konstanten oder bestimme die Länge mit .size() bei vector. Wenn du C++17 nutzen kannst, geht auch std::size - sowohl für Arrays als auch für Vectoren.

    Lies bitte https://de.wikipedia.org/wiki/Magische_Zahl_(Informatik)#Magische_Zahlen_in_Code



  • @wob Ah ja verstehe, ich dachte du beziehst dich mit Array Länge auf die "4" in std::string category [4]. Vielen Dank für deine Antwort und den sehr nützlichen Hinweis auf die Magischen Zahlen!