String zu enum



  • Hallo,

    ich bin noch neu in C++ und finde keine elegante Methode mein Problem zu lösen.
    Ich habe eine Datei voller Strings. Nun möchte ich diese Strings einlesen und in einem enum speichern.
    Also so ungefähr:

    #include <fstream>
    
    enum Color {Red, Green, Blue};
    
    int main() {
    Color color;
    std::ifstream ifs("color.txt");
    ifs >> color;
    };
    

    Nun meckert der Compiler natürlich, dass es dafür keinen geeigneten Operator gibt. Das ganze erst in einen String einzulesen und dann auf Fehler überprüfen erscheint mir aber viel zu komplex.
    Gibt es dafür auch eine elegantere Methode?



  • Du musst den Operator überladen.

    std::istream& operator >> (std::istream& is, Color& c)
    {
        std::string s;
        is >> s;
    
        if(s == "Red")
            c = Red;
    
        else if(s == "Green")
            c = Green;
    
        else if(s == "Blue")
            c = Blue;
    
        else
            is.setstate(ios_base::failbit);
    
        return is;
    }
    


  • #include <fstream>
    
    enum Color {Red=0, Green=1, Blue=2};
    
    int main() {
        Color color;
        std::ifstream ifs("color.txt");
        ifs >> color;
    };
    

    Und da sollen in der Datei auch 0 1 2 stehen. Schon ist das Problem weg.

    Nach dem Compilieren sind die Namen der enums weg. Du müßtest schon mit strings arbeiten und vergleichen udn so Sachen machen.


  • Mod

    Also in der Datei stehen richtige Worte a la "Red", "Green", "Blue"? Falls ja, gibt es da keinen direkten Weg, da die Bezeicher in deinem Programm (also die enum-Werte Red, Green, Blue) zur Laufzeit nicht mehr existieren. Eine relativ leichte Möglichkeit wäre es, eine map zu machen, die wirklich die Strings "Red", "Green" und "Blue" auf die passenden enum-Werte mappt 😉 . Dann liest du strings aus der Datei ein. Findest du den String in der map, nimmst du den gemappten Wert. Ansonsten Lesefehler. Das ganze kannst du in einem eigenen überladenen Operator für dein enum Color machen, dann sieht das Hauptprogramm praktisch genau so aus wie jetzt.



  • 314159265358979 schrieb:

    Du musst den Operator überladen.

    std::istream& operator >> (std::istream& is, Color& c)
    {
        std::string s;
        is >> s;
    
        if(s == "Red")
            c = Red;
    
        else if(s == "Green")
            c = Green;
    
        else if(s == "Blue")
            c = Blue;
    
        else
            is.setstate(ios_base::failbit);
    
        return is;
    }
    

    Lahm. Aber hab ich dir das nicht schon ein paarmal erklärt?



  • Da hast du Recht, an die map hab ich gerade überhaupt nicht gedacht.



  • volkard schrieb:

    #include <fstream>
    
    enum Color {Red=0, Green=1, Blue=2};//...
    

    Werden Green und Blue nicht implizit weiter enumeriert?
    Und Red automatisch der Wert 0 zugewiesen, wenn kein anderer angegeben wird?



  • Also habe ich das jetzt richtig verstanden, dass ich den istream-Operator überladen soll, aber anstatt der ganzen if-Abfragen von Pi, eine Map dafür verwenden soll?



  • Blattasar schrieb:

    anstatt der ganzen if-Abfragen von Pi, eine Map dafür verwenden soll?

    Die if-Abfragen sind wohl schneller bei den drei Werten. Aber je mehr Farben Du hast, desto langsamer werden sie. Irgenwann wird die map schneller, die ist nämlich beinahe unabhängig von der Anzahl der Werte. Aber hier würde ich nicht auf Geschwindigkeit achten. map klingt mir erstmal umständlicher.



  • volkard schrieb:

    Die if-Abfragen sind wohl schneller bei den drei Werten. Aber je mehr Farben Du hast, desto langsamer werden sie. Irgenwann wird die map schneller, die ist nämlich beinahe unabhängig von der Anzahl der Werte. Aber hier würde ich nicht auf Geschwindigkeit achten. map klingt mir erstmal umständlicher.

    Damit der Geschwindigkeitsunterschied gegenüber dem IO-Flaschenhals überhaupt mal auffällt, brauchst du schon einige verschiedene Farben. Aber was genau ist denn an der map umständlicher?



  • Michael E. schrieb:

    Aber was genau ist denn an der map umständlicher?

    Nur das Initialisieren: Singleton oder lokale statische Variable und von einer Funktion füllen lassen, die diese map returnt und dabei pro Zugriff ein if bezahlen oder globale Variable vor der main() initialisieren lassen mit ebendieser Funktion und dann theoretisch die Initialisierungsreihenfolge globaler Objekte richtig steuern müssen. Aufwand, der es nicht wert ist, denke ich, weil man enum-Namen nicht in Dateien schreibt.



  • Dass man enum-Namen nicht in Dateien schreibt, sehe ich auch so. Aber die map-Variante finde ich schöner als Copy&Paste-if-Kaskaden (über switch könnte man reden).



  • switch geht aber nicht bei std::string. 😉



  • if ist hässlicher als map ist hässlicher als find.

    Warum kein statisches char*-Array mit std::find?



  • Michael E. schrieb:

    Dass man enum-Namen nicht in Dateien schreibt, sehe ich auch so. Aber die map-Variante finde ich schöner als Copy&Paste-if-Kaskaden (über switch könnte man reden).

    Aber perfektes hashing darf man auch nehmen.
    Ach, jetzt, wo ich auf Linux bin, gib's das ja als fertiges Paket. Mal installieren und ausprobieren…

    vhmain ~ # echo Red > in.txt
    vhmain ~ # echo Green >> in.txt
    vhmain ~ # echo Blue >> in.txt
    vhmain ~ # gperf in.txt
    
    /* C code produced by gperf version 3.0.4 */
    /* Command-line: gperf in.txt  */
    /* Computed positions: -k'' */
    
    #define TOTAL_KEYWORDS 3
    #define MIN_WORD_LENGTH 3
    #define MAX_WORD_LENGTH 5
    #define MIN_HASH_VALUE 3
    #define MAX_HASH_VALUE 5
    /* maximum key range = 3, duplicates = 0 */
    
    #ifdef __GNUC__
    __inline
    #else
    #ifdef __cplusplus
    inline
    #endif
    #endif
    /*ARGSUSED*/
    static unsigned int
    hash (str, len)
         register const char *str;
         register unsigned int len;
    {
      return len;
    }
    
    #ifdef __GNUC__
    __inline
    #if defined __GNUC_STDC_INLINE__ || defined __GNUC_GNU_INLINE__
    __attribute__ ((__gnu_inline__))
    #endif
    #endif
    const char *
    in_word_set (str, len)
         register const char *str;
         register unsigned int len;
    {
      static const char * wordlist[] =
        {
          "", "", "",
          "Red",
          "Blue",
          "Green"
        };
    
      if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
        {
          register int key = hash (str, len);
    
          if (key <= MAX_HASH_VALUE && key >= 0)
            {
              register const char *s = wordlist[key];
    
              if (*str == *s && !strcmp (str + 1, s + 1))
                return s;
            }
        }
      return 0;
    }
    

    Naja, nicht schön, aber die Idee sollte klar werden. Ich hätte den Anfangsbuchstaben genommen. Die Länge ist aber auch süß.



  • Darf man fragen wieso der Code in C ist?



  • Hacker schrieb:

    Darf man fragen wieso der Code in C ist?

    Ja. Und Du kannst es sogar ganz alleine herausfinden.



  • volkard schrieb:

    Hacker schrieb:

    Darf man fragen wieso der Code in C ist?

    Ja. Und Du kannst es sogar ganz alleine herausfinden.

    gperf, so so ...



  • volkard schrieb:

    Hacker schrieb:

    Darf man fragen wieso der Code in C ist?

    Ja. Und Du kannst es sogar ganz alleine herausfinden.

    Ich war unfähig es herauszufinden.

    % gperf -L C++ in.txt | xclip -o
    

    Das kopiert bei mir folgenden Code in die Zwischenablage:

    /* C++ code produced by gperf version 3.0.3 */
    /* Command-line: gperf -L C++ in.txt  */
    /* Computed positions: -k'' */
    
    #define TOTAL_KEYWORDS 3
    #define MIN_WORD_LENGTH 3
    #define MAX_WORD_LENGTH 5
    #define MIN_HASH_VALUE 3
    #define MAX_HASH_VALUE 5
    /* maximum key range = 3, duplicates = 0 */
    
    class Perfect_Hash
    {
    private:
      static inline unsigned int hash (const char *str, unsigned int len);
    public:
      static const char *in_word_set (const char *str, unsigned int len);
    };
    
    inline /*ARGSUSED*/
    unsigned int
    Perfect_Hash::hash (register const char *str, register unsigned int len)
    {
      return len;
    }
    
    const char *
    Perfect_Hash::in_word_set (register const char *str, register unsigned int len)
    {
      static const char * wordlist[] =
        {
          "", "", "",
          "Red",
          "Blue",
          "Green"
        };
    
      if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
        {
          register int key = hash (str, len);
    
          if (key <= MAX_HASH_VALUE && key >= 0)
            {
              register const char *s = wordlist[key];
    
              if (*str == *s && !strcmp (str + 1, s + 1))
                return s;
            }
        }
      return 0;
    }
    

    Warum da inline steht, es aber nicht direkt inline geschrieben ist, kann ich zwar nicht verstehen, aber ich sehe, dass da C++ und nicht C/C++ (wie bei volkard) generiert wurde.



  • Michael E. schrieb:

    Dass man enum-Namen nicht in Dateien schreibt, sehe ich auch so. Aber die map-Variante finde ich schöner als Copy&Paste-if-Kaskaden (über switch könnte man reden).

    Der Meinung bin ich auch, jedoch kann ich den Dateiinhalt leider nicht bestimmen. Ich habe jetzt die std::find Variante genommen, da sie schön schlank aussieht 🙂


Log in to reply