Zeilen einlesen, Parameter speichern und später verwenden



  • Hallo zusammen,

    habe eine Textdatei die so oder so ähnlich aussieht:

    R50=20
    R30=195
    X=R30
    I=R30+R50
    

    die Textdatei soll nach dem durchlauf meines C++-Programmes aber so aussehen:

    R50=20
    R30=195
    X=195
    I=215

    Die Parameter sollen also gespeichert werden und später eingesetzt und das Ergebnis ausgegeben werden.

    Die Zeilen lese ich zur Zeit zeilenweise ein und würde nach dem Auftreten eines
    "R"-Parameters in der Zeile suchen. Nur wie speichere ich diese Variablen und gebe sie in der entsprechenden Zeile wieder aus...Vom verrechnen ganz zu schweigen.... 😞

    Hatte die Idee zu prüfen, ob nach dem "="-Zeichen noch ein "+" oder "-" auftritt, wo das Programm dann die gespeicherten Werte sucht und einsetzt. Leider bleibt es aber nur bei der Idee, an der Umsetzung scheitert es...

    Kann mir da jemand helfen, oder gibt es sogar eine bessere Idee... 🙄



  • Du kannst die Variablen in ner std::map<std::string, int> halten (Name, Wert). Beim Parsen kannst du, wie du schon richtig vermutet hast, prüfen, ob das erste Zeichen ein 'R' ist, und gegebenenfalls in deiner map nach dieser Variablen suchen.

    Ich nehme an, dass du noch nicht viel Erfahrung mit C++ hast. Muss es denn diese Sprache sein? Für solche kleinen Aufgaben eignen sich andere Sprachen besser, z. B. Python oder Ruby. Da kannst du nen regulären Ausdruck draufhauen (/R\d+/) und gut ist.



  • Wird/Ist regex nicht schon Teil von "TR1" und somit bald/jetzt in C++? Ich meine das einige Compiler dies schon unterstützen.

    Ob dies dann kommerziell verwendet werden sollte ist eine andere Frage. Auf jedenfall findet man regex auch in "Boost", welches aber einen Anfänger überfordern wird (installation, konfiguration, build etc.).



  • Ich fand die Installation von Boost alles andere als kompliziert. Die beiliegende Anleitung hat in wenigen Schritten durch die Konfiguration geführt. Man sollte nur jetzt nicht erwarten einen Installer vorzufinden. Wer aber damit überfordert ist, der sollte sein Wissen allgemein mal auffrischen. Das sind alles Grundlagen, ohne die ich beim Programmieren auch sonst nicht weit komme.
    Davon abgesehen würde ich an der Stelle auch eher zu Perl greifen.



  • TheodorN. schrieb:

    .. oder gibt es sogar eine bessere Idee... 🙄

    Hallo Theodor,

    ja es gibt eine Methode um so was in C++ zu lösen. Ist aber alles andere als einfach! Also fange ich mal klein an.

    Mal angenommen, da stehen nur Zahlen hinter den Variablen in der Textdatei (ich unterstelle es sind int - mit double ginge es aber genauso), dann liest man den Variablennamen mit getline und anschließend den Wert:

    #include <iostream>
    #include <fstream>
    
    int main()
    {
        using namespace std;
        ifstream in( "input.txt" ); // die Textdatei
        if( !in.is_open() )
        {
            cerr << "Fehler beim Oeffnen der Textdatei" << endl;
            return -1;
        }
        string var_name;
        for( int x; getline( in >> ws, var_name, '=' ) >> x; )
            cout << var_name << '=' << x << endl;;
    
        return 0;
    }
    

    bis hierin war's noch recht einfach.

    Im nächsten Schritt möchte ich die Variablennamen lesen können. Bevor das funktioniert, sollte man sich überlegen, wie man an die rankommt. Beim Lesen eines Typs hat man nur den Stream (std::istream) und den Variablennamen (der gelesen wird). Um jetzt an den Wert der Variable zu kommen, nutze ich die Methode ios_base::pword. Damit kann man beliebige Zeiger an einen Stream anheften und diese später wieder abholen.
    Zudem benötigt man natürlich einen Container, um die Werte zu speichern. Wie schon von Michael E. vorgeschlagen ist eine map<string,int> eine gute Wahl. Es gilt also später beim Lesen an diese Map heranzukommen. Dazu ein paar Helferlein:

    #include <map>
    #include <iostream>
    #include <string>
    
    namespace
    {
        const int VARIABLEN_KONTEXT = std::ios_base::xalloc();
    }
    
    typedef std::map< std::string, int > Kontext;
    void setze_kontext( std::istream& in, Kontext& k )
    {
        in.pword( VARIABLEN_KONTEXT ) = &k;
    }
    int hole_variable_aus_kontext( std::istream& in, const std::string& var_name )
    {
        const Kontext* k = reinterpret_cast< const Kontext* >( in.pword( VARIABLEN_KONTEXT ) );
        if( k )
        {
            Kontext::const_iterator iVar = k->find( var_name );
            if( iVar != k->end() )
                return iVar->second;
        }
        in.setstate( std::ios_base::failbit );
        return 0;
    }
    

    Die Idee ist, eine Map (hier mal als Kontext bezeichnet) zur Verfügung zu stellen, die mit setze_kontext dem Lesestream bekannt gemacht wird. Später beim Lesen kann man dann via hole_variable_aus_kontext unter Angabe des Variablennamens den Wert holen. Wird der Wert nicht gefunden, so wird das Fehlerflag im Stream gesetzt.

    Mit Hilfe der obigen Routinen lässt sich jetzt eine Klasse Variable bauen, die die Variablen aus der Textdatei lesen soll.

    struct Variable
    {
        typedef int value_type; // da könnte genausogut 'typedef double value_type;' stehen
    
        friend std::istream& operator>>( std::istream& in, Variable& r )
        {
            std::string var_name;
            if( read_varname( in, var_name ) )
                r.m_x = hole_variable_aus_kontext( in, var_name );
            else
                in >> r.m_x;
            return in;
        }
        value_type Val() const { return m_x; }
    
    private:
        value_type m_x;
    };
    

    Die Lesefunktion von Variable versucht zunächst einen Namen zu lesen (der z.B. mit 'R' beginnt) und wenn das gelingt, wird der zugehörige Wert aus dem Kontext geholt. Ist dort kein Name wird einfach der Wert gelesen, der da folgt.

    Bleibt noch die Funktion 'read_varname' offen. Diese Funktion muss einen Variablennamen im stream erkennen, darf aber kein Zeichen lesen (genauer konsumieren), wenn keine Variable folgt. Weil anschließend der Wert gelesen werden soll. Hier die Implementierung, wobei ich davon ausgegangen bin, dass so ein Name immer das Format "R<Digits>" hat. Das kann man natürlich ändern - die entsprechenden Stellen stehen in den Zeilen 11 und 23 des folgenen Listings. Einzige Bedingung: ein Name muss am ersten Zeichen erkennbar sein. Das kann ein 'R' sein oder auch ein beliebiger Buchstabe, da führende Buchstaben ansonsten nicht in dem Ausdruck vorkommen.

    bool read_varname( std::istream& in, std::string& var_name_ )
    {
        using namespace std;
        istream::sentry ok( in );
        if( !ok )
            return false;
    
        typedef istream::traits_type Traits;
        const ctype< char >& ctype_ = use_facet< ctype< char > >( in.getloc() );
        istream::int_type m = in.rdbuf()->sgetc(); // Bem.: 'm' kann nicht EOF sein, da 'in' ok ist.
        if( Traits::to_char_type( m ) != 'R' )  // hier wird entschieden, ob es sich um einen Variablennamen handelt
            return false;   // keine Variable
    
        string var_name( 1, Traits::to_char_type( m ) );
        for( m = in.rdbuf()->snextc(); ; m = in.rdbuf()->snextc() )
        {
            if( Traits::eq_int_type( m, Traits::eof() ) )
            {
                in.setstate( ios_base::failbit );
                break;
            }
            const char c = Traits::to_char_type( m );
            if( !ctype_.is( ctype_base::digit, c ) )
                break;  // Ende des Variablennamens
            var_name.push_back( c );
        }
        swap( var_name, var_name_ ); // eingelesenen Namen übernehmen
        return true;    
    }
    

    Jetzt kann man im main einfach den int durch Variable austauschen; und den Kontext nicht vergessen.

    int main()
    {
        using namespace std;
        ifstream in( "input.txt" ); // die Textdatei
        if( !in.is_open() )
        {
            cerr << "Fehler beim Oeffnen der Textdatei" << endl;
            return -1;
        }
        Kontext variablen;
        setze_kontext( in, variablen );
    
        string var_name;
        for( Variable x; getline( in >> ws, var_name, '=' ) >> x; )
        {
            variablen[ var_name ] = x.Val(); // wichtig, die gelesenen Werte merken, damit sie später zur Verfügung stehen
            cout << var_name << '=' << x.Val() << endl;
        }
    
        if( !in.eof() )
            cerr << "Lesen mit Fehler abgebrochen " << endl;
    
        return 0;
    }
    

    Enthält die Textdatei

    R50=20
    R30=195
    X=R30
    I=R50
    

    so ist die Ausgabe des Programms

    R50=20
    R30=195
    X=195
    I=20
    

    Im dritten Schritt sollen noch Rechenoperationen berücksicht werden, die Variablen und Konstanten verknüpfen. Ich habe dazu vor einiger Zeit einen Recursive Descent Parser geschrieben. Den Sourcecode findest Du hier <http://www.c-plusplus.net/forum/p1642138>. Packe ihn einfach in eine H-Datei (exclusive des dortigen mains natürlich) und inkludiere ihn hier.

    Dann muss nur noch die Zeile 14 im letzten Listing geändert werden nach:

    for( Variable x; getline( in >> ws, var_name, '=' ) >> expression( x ); )
    

    .. und da in dem Parser mit dem Typ Variable auch gerechnet wird, muss der noch die Grundrechenarten lernen - es fehlen also noch folgende Methoden:

    struct Variable
    {
        typedef int value_type; // da könnte genausogut 'typedef double value_type;' stehen
        // --   Grundrechenarten
        Variable& operator+=( Variable b ) { m_x += b.m_x; return *this; }
        Variable& operator-=( Variable b ) { m_x -= b.m_x; return *this; }
        Variable& operator*=( Variable b ) { m_x *= b.m_x; return *this; }
        Variable& operator/=( Variable b ) { m_x /= b.m_x; return *this; }
    
        Variable& operator*=( int i ) { m_x *= i; return *this; } // dient der Berücksichtigung eines Vorzeichens
        // usw. (s.o.)
    

    Steht in der Datei

    R50=20
    R30=195
    X=R30*(R30-95)
    I=R50+12
    

    so ist die Ausgabe des Programms nun:

    R50=20
    R30=195
    X=19500
    I=32
    

    Viel Spaß damit
    Gruß
    Werner



  • Hey Werner,

    immer wieder beeindruckend deine Lösungs-Beispiele.
    Hast du dir mal überlegt ein Buch a la: "C++ IOSTREAMS - praktische Anwendungsfälle" zu schreiben?

    Ich würde es kaufen.;)



  • TMI aber cool.


Anmelden zum Antworten