Einlesen einer Datei



  • Hallo,

    ich arbeite an einem Projekt, in dem wir eine Datei in eine std::map einlesen sollen. Also eine Koordinatenliste bestehend aus Koordinate und Adjazenzliste.

    Die Datei ist wie folgt aufgebaut:

    1 1;1;1 2;
    1 2;2;2 2;1 3;
    1 3;2;2 3;1 4;
    1 4;2;2 4;1 5;
    1 5;2;2 5;1 6;
    1 6;2;2 6;1 7;
    .
    .
    38 29;2;38 28;37 29;
    38 30;2;38 29;37 30;
    38 31;1;38 30;
    

    Erläuterung:

    Das erste Pärchen x y steht für die Hauptkoordinate. Das ; ist das Trennzeichen. Die Zweite Zahl ist die Anzahl der Adjazenzknoten, dann folgen die Adjazenzknoten.

    Ich habe zwar etwas geschrieben, was auch funktioniert, allerdings hoffe ich, dass das auch etwas einfacher geht. Meine Readin Methode hat locker 100 Zeilen(bei Bedarf poste ich diese auch).

    Es geht also datum, von jeder Koordinate die x- und yAchse einzeln in eine Variable zu speichern, die Hauptkoordinate direkt in meine nodes[loc] zu speichern und die Adjazenzknoten in einer Adjazenzliste zu speichern und dann auch in die Map einzufügen.

    Wie gesagt ich finde meine Methode sehr umständlich und erhoffe mir Tipps, wie ich das um einiges reduzieren kann.

    MfG
    skizZ



  • Dann zeigst du den Code am besten mal. Man kann auch auf umständliche Arten einlesen. 😉



  • Hi,

    ja kann man, mir gefällt es aber nicht 😞

    void Graph::readMap()
    {
      char line[100];
      string filePath = "";
      filePath += fileName;
      ifstream fin;
      fin.open(filePath.c_str());
      if(!fin.good()) 
      {
        std::cout << "Kann Datei nicht öffnen!";
    	exit(1);
      }
      while (fin.eof() == 0)
      {
        fin.getline(line,100);
        int xloc = line[0] - 48;
        int yloc = 0;
        int i = 0;
        if (line[1] != ' ')
        {
          xloc = (xloc*10) + (line[1] - 48);
          yloc = line[3] - 48;
          if (line[4] != ';')
          {
            yloc = (yloc*10) + (line[4] - 48);
            i = 8;
          }
          else
            i = 7;
          }
          else
          {
            yloc = line[2] - 48;
            if (line[3] != ';')
            {
              yloc = (yloc*10) + (line[3] - 48);
              i = 7;
            }
            else
              i = 6;
          }
          Location stLoc(xloc, yloc);			//stammlocation
          List<Location> adjList;				//adjazenzliste
    	  bool twoDigitsX = false;
          bool twoDigitsY = false;
          int x = 0;
          int y = 0;
          while (line[i] != '#')
          {
          if (line[i-1] == ';')
          {
            if (line[i+1] == ' ')
            x = line[i] - 48;
            else
            {
              twoDigitsX = true;
              x = line[i] - 48;
              x *= 10;
            }
          }
          else if (twoDigitsX == true)	
          {
            x += line[i] - 48;
            twoDigitsX = false;
          }
          else if (line[i-1] == ' ')
          {
            if (line[i+1] == ';')
              y = line[i] - 48;
            else
            {
              twoDigitsY = true;
              y = line[i] - 48;
              y *= 10;
            }
          }
          else if (twoDigitsY == true)	
          {
            y += line[i] - 48;
            twoDigitsY = false;
          }
          else if (line[i] == ';')
            adjList.push_back(Location(x,y));
          ++i;
        }
        nodes[stLoc] = Node(stLoc);			//in map-container speichern
        nodes[stLoc].adjList = adjList;
      }
      fin.close();
    }
    


  • skizZ schrieb:

    ja kann man, mir gefällt es aber nicht 😞

    .. um ehrlich zu sein - da ist Verbesserungspotential.

    skizZ schrieb:

    void Graph::readMap()
    {
      ...
        fin.getline(line,100);
    

    Das scheint in vielen Büchern und Schulungsunterlagen zu stehen, dass man Dateien immer mit getline einlesen soll. Was aber sehr umständlich ist, weil getline eben Zeilen liest - wie der Name schon sagt, aber weder Koordinaten noch Listen von Adjazenzknoten.

    Ich unterstelle mal, es gibt eine Struktur/Klasse Location mit X- und Y-Koordiante und eine Struktur/Klasse Node mit einer Location und einer Liste von Locations - wobei ich hier als Liste die std::list hernehme, da ich List nicht kenne.

    Dann sieht mein Vorschlag so aus:

    #include <fstream>
    #include <list>
    #include <iostream>
    #include <map>
    #include <string>
    
    // --   Helferlein zum Einlesen eines erwarteten Zeichens
    template< char C >  
    std::istream& Char( std::istream& in )
    {
        char c;
        if( in >> c && c != C )
            in.setstate( std::ios_base::failbit );
        return in;
    }
    
    struct Location
    {
        bool operator<( const Location& b ) const
        {
            return m_y < b.m_y || ( m_y == b.m_y && m_x < b.m_x );
        }
        friend std::istream& operator>>( std::istream& in, Location& loc )
        {   // liest eine Location (Koordinate)
            return in >> loc.m_x >> loc.m_y;
        }
        double /*oder int?*/ m_x, m_y;
    };
    struct Node
    {
        friend std::istream& operator>>( std::istream& in, Node& node )
        {   // liest einen Knoten mit Stammkoordinate und Liste der Adjazenzknoten
            int n; // Anzahl der Adjazenzknoten
            if( in >> node.m_stamm >> Char<';'> >> n >> Char<';'> )
            {
                std::list< Location > adjList;
                for( Location adjazenz; n && in >> adjazenz >> Char<';'>; --n )
                    adjList.push_back( adjazenz );
                if( in )    // ohne Fehler 'n' Knoten gelesen
                    swap( adjList, node.m_adjList ); // dann übernehmen
            }
            return in;
        }
        Location m_stamm;
        std::list< Location > m_adjList;
    };
    
    std::istream& readMap( std::istream& in, std::map< Location, Node >& nodes_ )
    {
        std::map< Location, Node > nodes;
        for( Node node; in >> node; )
            nodes[ node.m_stamm ] = node;
        if( in.eof() )  // bis EOF gelesen
        {
            in.clear(); // dann war's ok
            swap( nodes, nodes_ );
        }
        return in;
    }
    
    int main()
    {
        using namespace std;
        string filePath = "C:/Temp/input.txt";
        map< Location, Node > nodes;
        ifstream fin( filePath.c_str() );
        if( readMap( fin, nodes ) )
            cout << nodes.size() << " Knoten gelesen" << endl;
        return 0;
    }
    

    Gruß
    Werner



  • Hallo,

    super. ich konnte es so in dieser Art umsetzen und es funktioniert einwandfrei. allerdings habe ich noch ein Paar Fragen dazu, da ich das nicht so richtig verstehe.

    Was genau machen

    template< char C >  
    std::istream& Char( std::istream& in )
    {
        char c;
        if( in >> c && c != C )
            in.setstate( std::ios_base::failbit );
        return in;
    }
    
    friend std::istream& operator>>( std::istream& in, Node& node )
        {   // liest einen Knoten mit Stammkoordinate und Liste der Adjazenzknoten
            int n; // Anzahl der Adjazenzknoten
            if( in >> node.m_stamm >> Char<';'> >> n >> Char<';'> )
            {
                std::list< Location > adjList;
                for( Location adjazenz; n && in >> adjazenz >> Char<';'>; --n )
                    adjList.push_back( adjazenz );
                if( in )    // ohne Fehler 'n' Knoten gelesen
                    swap( adjList, node.m_adjList ); // dann übernehmen
            }
            return in;
        }
    
    std::map< Location, Node > nodes;
        for( Node node; in >> node; )
            nodes[ node.m_stamm ] = node;
        if( in.eof() )  // bis EOF gelesen
        {
            in.clear(); // dann war's ok
            swap( nodes, nodes_ );
        }
        return in;
    


  • skizZ schrieb:

    Was genau machen ..

    template< char C >  
    std::istream& Char( std::istream& in )
    {
        char c;
        if( in >> c && c != C )
            in.setstate( std::ios_base::failbit );
        return in;
    }
    

    das ist zunächst einmal eine Funktion, für die der std::istream einen sogenannten Inserter hat. D.h. ein Objekt der Klasse std::istream hat einen operator>> dessen zweiter Paramter eine Funktion mit dieser Signatur sein kann. In dem Inserter wird dann diese Funktion aufgerufen.
    Also

    cin >> Char<';'>
    

    hat den gleichen Effekt wie:

    Char<';'>( cin );
    

    Der Aufruf bewirkt dann:

    template< char C >  
    std::istream& Char( std::istream& in )
    {
        char c;
        in >> c; // Einlesen eines Zeichens
        if( !in.fail() ) // prüfe das Fehlerbit
        {
            // es ist kein Fehler aufgetreten
            if( c != C )
            {
                  // das eingelesene Zeichen 'c' ist nicht gleich C
                  in.setstate( std::ios_base::failbit ); // dann setze das Fehler-Bit
            }
        }
        return in;
    }
    

    es wird ein Zeichen gelesen und falls dies nicht identisch mit dem Template-Parameter C ist, wird ein Fehler gesetzt.

    friend std::istream& operator>>( std::istream& in, Node& node )
        {   // liest einen Knoten mit Stammkoordinate und Liste der Adjazenzknoten
            int n; // Anzahl der Adjazenzknoten
            if( in >> node.m_stamm >> Char<';'> >> n >> Char<';'> )
            {
                std::list< Location > adjList;
                for( Location adjazenz; n && in >> adjazenz >> Char<';'>; --n )
                    adjList.push_back( adjazenz );
                if( in )    // ohne Fehler 'n' Knoten gelesen
                    swap( adjList, node.m_adjList ); // dann übernehmen
            }
            return in;
        }
    

    dies ist eine Funktion, die ein Objekt der Klasse Node vom std::istream liest - bzw. ein Streaming-Operator für Node.
    Die Funktion ganz aufgedröselt:

    friend std::istream& operator>>( std::istream& in, Node& node )
        {   // liest einen Knoten mit Stammkoordinate und Liste der Adjazenzknoten
            in >> node.m_stamm; // lese die Stamm-Koordinate
            in >> Char<';'>; // lese ein ';', falls kein ';' da, wird das Fehlerbit gesetzt (s.o.)
            int n; // Anzahl der Adjazenzknoten
            in >> n; // lese die Anzahl der Adjazenzknoten
            in >> Char<';'>; // lese wieder ein ';', falls kein ';' da, wird das Fehlerbit gesetzt (s.o.)
            if( !in.fail() )  // prüfe Fehlerbit
            {   // falls bis hierher nirgendwo das Fehlerbit gesetzt wurde ...
                std::list< Location > adjList;
                for( Location adjazenz; n && in >> adjazenz >> Char<';'>; --n ) // Lese bis n == 0 ist oder ein Fehler auftritt
                    adjList.push_back( adjazenz ); // Koordinate 'adjazenz' ohne Fehler gelesen, also in Liste aufnehmen
                if( in )    // ohne Fehler 'n' Knoten gelesen
                    swap( adjList, node.m_adjList ); // dann übernehmen, tausche den Inhalt von 'adjList' und 'node.m_adjList'
            }
            return in;
        }
    

    Die Funktion liest die Bestandteile eines Knotens (Node) und füllt das Node-Objekt damit.

    Und das dritte Code-Schnipsel:

    std::map< Location, Node > nodes;
        for( Node node; in >> node; ) // Lese Knoten (Node-Objekte) bis ein Fehler auftritt
            nodes[ node.m_stamm ] = node; // Lesen war bisher ok, dann node in map aufnehmen
        if( in.eof() )  // tritt ein Fehler auf, weil die Datei am Ende ist, so ist das ok, am Ende wird das EOF-Bit gesetzt
        {
            in.clear(); // dann war's ok
            swap( nodes, nodes_ ); // tausche den Inhalt von 'nodes' und 'nodes_'
        }
        return in;
    

    Gruß
    Werner


Log in to reply