Zeichenkette auf double Zahl prüfen mit Schleife und Case Statement

DerC++Noob

Grünschnabel
Hey Com,

Ich benötige etwas Hilfe beim programmieren meiner Funktion. Sie soll eine Zeichenkette mit Hilfe eines Zustandsautomaten (mit Schleife und Case‐Statement) Zeichen für Zeichen überprüfen, ob es sich um eine gültige Eingabe einer Zahl im Double‐Format handelt.


bool checkdoublenumber(const string& input){
string input;
for(int j=0;j<=input.size();j++){
char zeichen = input
;
switch(input
){
case '-' :
if(input[0] = true)
else cout << "invalid" << endl;
}break;
case '.':
if(input[1] = true)
if else (input[2] = true)
cout << "invalid" <<endl;
}break;
case ',' :
{
}break;
case 'e' ://für kleine zahlen
{
}break;
case 'x' ://
{
}break;
default
cout << "valid"
}
}

ich hoffe ihr könnt mir weiter helfen...
 

cwriter

Erfahrenes Mitglied
ich hoffe ihr könnt mir weiter helfen...
Öh ja.
Wie du unschwer erkennen kannst, ist dein Code hier im Forum sehr zerschossen. Mit Code Tags ([code=cpp][/code]) gäbe es dieses Problem nicht.

Dennoch, soweit ich das beurteilen kann, ist dein Code ziemliches Gemüse.
Zustandsautomaten
Nennen wir es FSM (Finite State Machine).

Die erste Frage ist bei sowas immer: Was ist denn eine "gültige" double-Zahl? Darf es whitespaces davor und danach haben? Nur wissenschaftliche Darstellung oder alles? Ist eine Eingabe gültig, wenn die Präzision von Double dafür gar nicht reicht?
Im Prinzip ist ein Double so darstellbar:

(-)([Vorkomma])(.)([Nachkomma])({e/E}[(-)Exponent])

Wobei alles in "()" optional ist.
Nun sieht man aber schon: Das geht so nicht, denn so wäre ein leerer String eine gültige Eingabe. Ok, fügen wir den leeren String einfach als Ausnahme für 0 hinzu.
Ebenso kann ein Minus nicht alleine stehen, also auch das als Ausnahme für 0.
Wir haben dann also folgende States:
Vorzeichen: Das nächste Zeichen muss auf '-' geprüft werden. Ist es kein Minus, Dann wechsle zu "Vorkomma".
Vorkomma: Nur Ziffern 0-9 sind gültig. Falls '.', wechsle zu "Nachkomma".
Nachkomma: Nur Ziffern 0-9 sind gültig. Falls 'e' oder 'E', wechsle zu "Exponent".
Exponent: Falls '-', Vorzeichen. Danach ist '-' ungültig. Weiter sind nur 0-9 gültig.

Ok, nun haben wir diese States.
C++:
enum States
{
Vorzeichen,
Vorkomma,
Nachkomma,
Exponent
}

//und dann ein Loop über alles...
States state = Vorzeichen;

for(size_t i = 0; i < str.size(); i++)
{
char c = str[i];
if(state == Vorzeichen)
{
if(c == '-') state = Vorkomma;
else if(isdigit(c)) state = Vorkomma;
else if(c == '.') state = Nachkomma;
else return false; // ungültiges Zeichen
}
else if(state == Vorkomma)
{
//...
}
}

Und so weiter. Ein Switch-Statement würde ich hier nicht verwenden wollen, aber im Prinzip kannst du "switch(state)" statt "if(state == ...)" machen.

Bei konkreten Fragen gerne nachfragen.

Gruss
cwriter
 
Zuletzt bearbeitet:

DerC++Noob

Grünschnabel
Vielen Dank für deine schnelle Hilfe cwriter :),
du hast mir sehr damit geholfen. Könntest du mir vielleicht Zeile 18 noch genauer erklären ? So wie ich das verstanden habe prüft isdigit den String auf Zahlen. Falls Zahlen vorkommen dann ? ich verstehe das mit dem Vorkomma nicht ganz

Gruss
DerC++Noob
 

cwriter

Erfahrenes Mitglied
So wie ich das verstanden habe prüft isdigit den String auf Zahlen. Falls Zahlen vorkommen dann ?
isdigit() überprüft auf Ziffern.
Der Grund hierfür ist ein bisschen kompliziert:
Normalerweise würde man hier "peeken" wollen, d.h. vorausschauen, ohne das Zeichen selbst zu verbrauchen (engl. "consume").
Sagen wir, du wolltest nicht nur prüfen, ob es eine gültige Zahl ist, sondern sie auch auslesen.
Dann müsstest du alle Vorkommazahlen im State "Vorkomma" einlesen. Da es aber ein Vorzeichen geben kann (dieses klassischerweise aber nur für '-' angegeben wird), musst du das erste Zeichen speziell betrachten.
Hier wäre switch sogar praktischer als else-if:
C++:
switch(state)
{
    case Vorzeichen:
        if(c == '-') { state = Vorkomma; break; }
        else if(c == '.') { state = Nachkomma; break; }
        else if(!isdigit(c)) return false; //ungültig
        //Ansonsten sickere durch zum nächsten Case
    case Vorkomma:
        //Hier kann man jetzt alle Ziffern direkt verarbeiten...
    //...
}
Denn so muss man c nicht schon im state "Vorzeichen" verbrauchen.

Falls Zahlen vorkommen dann ?
Falls Zahlen vorkommen, dann sind wir eigentlich im falschen State (eine Zahl ist kein Vorzeichen) und wir versuchen mehr schlecht als recht, zu "Vorkomma" zu wechseln. Mit dem Switch-Statement geht das besser.

Aber ich bin mir nicht sicher, ob du die States verstanden hast.
States bestimmen, wie mit den Eingaben verfahren werden soll.
Hier bestimmt der State schlicht, welche Eingaben gültig sind. Falls ein Trennzeichen auftritt (hier '-', '.', 'e'), dann muss zum nächsten State gewechselt werden. Wenn ein Zeichen im einen State ungültig ist, aber im nächsten gültig sein wird, dann muss in den nächsten State gewechselt werden (Ziffern sind als Vorzeichen ungültig, aber als Vorkommastelle gültig).

Bei diesem Code muss der Fall "." speziell behandelt werden, denn
0.0 ist eine gültige Double-Darstellung,
0. ist gültig und dasselbe,
.0 ist gültig und gleichwertig, aber
. ist keine Zahl mehr. Daher müsstest du zusätzlich speichern, ob mindestens eine Ziffer vorkam.
Man kann nun diskutieren, ob "0" auch eine gültige Double-Darstellung ist. Je nach Auffassung muss ein '.' vorkommen - oder halt nicht.

ich verstehe das mit dem Vorkomma nicht ganz
Vorkomma ist hier schlicht der Name des State. Du kannst auch Zahlen oder Strings für die Benennung verwenden.

Gruss
cwriter
 

DerC++Noob

Grünschnabel
Ich danke dir sehr für deine Mühe:(, ich weis das das nicht einfach ist...
ich habe den Code mal weiter geschrieben und wollte fragen ob das so passen könnte
 

Anhänge

  • funktion.cpp
    1,1 KB · Aufrufe: 10

cwriter

Erfahrenes Mitglied
ich habe den Code mal weiter geschrieben und wollte fragen ob das so passen könnte
Sofern du nicht einen ultranetten Compiler hast: Sicher nicht.
(Und Compiler sind selten nett).
Ich füge mal den Code hier ein, für alle anderen:
C++:
//============================================================================
// Name        : funktion.cpp
// Author      : Denis
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
using namespace std;

string str;

int main() {

    cin << string;
    enum states
    {
        Vorzeichen,
        Vorkomma,
        Nachkomma,
        Exponent,
    };
    for(;;){

States state = Vorzeichen;

for(size_t i = 0; i < str.size(); i++)
{
char c = str[i];
switch(state)
{
case Vorzeichen:
if(c == '-') { state = Vorkomma; break; }
else if(c == '.') { state = Nachkomma; break; }
else if(!isdigit(c)) return false;
case Vorkomma:
 if(c == '0','1','2','3','4','5','6','7','8','9') { state = Vorkomma; break; }
 else if (c == '.') { state = Nachkomma; break;}
case Nachkomma:
    if(c == '0','1','2','3','4','5','6','7','8','9') { state = Nachkomma; break; }
    else if (c == 'e','E') { state = Exponent; break;}
case Exponent:
    if(c == 'e', 'E') { state = Exponent; break;}
default:
    cout << "Keine gültige eingabe" << endl;
}
    }
}
1) Warum machst du "c == '0', '1', ..." wenn ich dir die Funktion isdigit() vorgeführt habe? Erstens geht ein Vergleich so gar nicht und zweitens ist die Alternative "c == '0' || c == '1' || ..." unglaublich lange und unübersichtlich. Für dein c == 'e', 'E' müsstest du c == 'e' || c == 'E' verwenden.
2) C++ ist Case-Sensitive. states != States.
3) Bei einer Enumeration darf am Schluss nie ein Komma stehen.
4) Im State "Exponent" darf 'e'/'E' nicht mehr vorkommen.
5) Ein State, der sich selbst wieder setzt, ist redundant (nichts ändert sich -> kein Assignment notwendig).
6) Im State Nachkomma wird der State Exponent erreicht, falls !isdigit(c). Falls ein Buchstabe != 'e' vorkommt (was den String ungültig macht), müsste "False" zurückgegeben werden.
7) Vom State Exponent wird immer "Keine gültige Eingabe" erreicht. Nutze break.
8) Dein "for(;;)"-Loop ist sinnlos, du gehst einfach immer wieder über denselben String...
9) Du solltest eine Funktion schreiben, nicht einfach alles in main() packen.
bool checkdoublenumber(const string& input)

Für ein nächstes Mal: Warum sollte ich Compiler spielen und offensichtliche Syntaxfehler herausheben wollen?
Jage den Code doch einfach kurz durch einen Compiler, dann sind Dinge wie 1) und 2) und 3) schnell behoben und ich kann mich auf die Semantik konzentrieren.

Da du keine andere Frage als "Stimmt das?" gestellt hast, ist die Antwort "Nein.".
Stelle konkrete Fragen, statt einfach per Brute-Force durchzuprobieren.
Fragen werden hier wie auch anderswo gerne beantwortet, aber auf offensichtlich (~syntaktisch) falschen Code haben wir keine Lust.
Oder anders ausgedrückt: Wir helfen bei:
1) Konzeptuellen Fragen ("Wie geht/funktioniert das?")
2) "Wo ist das Problem"-Fragen (wir erwarten hier aber dokumentierte Ein- und Ausgaben).
3) Alles in gewissem Rahmen, ausgenommen:
a) "Macht mir die Hausaufgaben"
b) "Hier habe ich zusammengewürfelten Code, er hat ein Problem. Bitte Problem finden und beheben"
c) "Mein Code hat einen Fehler. Hier sind 1000 Zeilen Code. Ich habe weder das Problem lokalisiert noch gebe ich die Fehlermeldung an. Sucht mal".

Die Quintessenz: Wir erwarten etwa gleich viel Arbeit vom Fragesteller wie vom Antworter.
Meine Beiträge kosten mich regelmässig eine halbe Stunde.
Wie lange hattest du, um diese ~10 Zeilen hinzuzufügen?
Wie viel Aufwand wäre es für dich, den Code schnell durch einen Compiler zu jagen?

Gruss
cwriter
 

DerC++Noob

Grünschnabel
Ok ich sehe ein das das egoistisch von mir war soviel zu verlangen, ohne Arbeit bzw ein wenig Arbeit reinzustecken. Ich habe mittlerweile das Programm fast fertiggestellt. Das Programm rechnet auch wie es soll nur erkennt das Programm nicht das (4-e) ein Fehler ist. Bei den anderen Eingaben erkennt das Programm die "falsche" Eingabe und beendet das Programm.
Kann mir jemmand sagen wie ich das noch lösen könnte ?

Ich habe mir überlegt das so zu machen das ich die Eingabe mit dem Case Vorzeichen und einem Integer vergleiche:
while(Vorzeichen = True and c == b)
do...return false;

so ähnlich

der Case Exponent ist ebenfalls Baustelle also nicht wundern wenn da was nicht passt.
 

Anhänge

  • Neues Textdokument (3).txt
    2,9 KB · Aufrufe: 7

cwriter

Erfahrenes Mitglied
Hi

Ich habe das mal durch den Kate auto-aligner gelassen, um es ein bisschen einzurücken.

C++:
//============================================================================
// Name        : Lab2.cpp
// Author      : Denis
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
#include<stack>
#include<sstream>



using namespace std;
bool operatoren(const string& input);
bool checkdoublenumber(const string& input);
void opausfueren(const string& input, stack<double>& RechnerStack);
// Main
int main() {
    cout << "Der UPN Taschenrechner" << endl; // prints !!!Hello World!!!
    stack<double> RechnerStack;
    string input;
    while(true)
    {
        cout << ">>";
        cin >> input;
        double Zahl;
        if(istringstream(input) >> Zahl)
        {
            RechnerStack.push(Zahl);
        }
        else if (checkdoublenumber(input))
        {
        }
        else if (operatoren(input))
        {
            opausfueren(input, RechnerStack);
        }
        else if (input == "q" || "x")
        {
            return 0;
        }
        else
        {
            cout << "Invalid input" << endl;
        }
    }
}
// Funktionen
bool operatoren(const string& input)
{
    string operatoren[] = {"-", "+", "*", "/"}; // string array mit verfügbaren Operatoren
  
    for(int i=0; i<4;i++)
    {
        if(input == operatoren[i]) // wenn
        {
            return true;
        }
    }
    return false;
}
void opausfueren(const string& input, stack<double>& RechnerStack)
{
    double zahl1, zahl2, ergebnis;
    zahl2 = RechnerStack.top();
    RechnerStack.pop();
    zahl1 = RechnerStack.top();
    RechnerStack.pop();
    if (input == "-")
    {
        ergebnis = zahl1 - zahl2;
    }
    else if (input == "+")
    {
        ergebnis = zahl1 + zahl2;
    }
    else if (input == "*")
    {
        ergebnis = zahl1 * zahl2;
    }
    else
    {
        ergebnis = zahl1 / zahl2;
    }
    cout << ergebnis << endl;
    RechnerStack.push(ergebnis);
}
////////////////////////////////////////////////////////////////////////////
bool checkdoublenumber(const string& input){
    enum states{
        Vorzeichen,
        Vorkomma,
        Nachkomma,
        Exponent
    };
    states state = Vorzeichen;
    for(unsigned int i = 0; i < input.size(); i++)
    {
        char c = input[i];
        {
        }
        switch(state){
            case Vorzeichen:
                if(c == '-') {
                    state = Vorkomma;
                }
                else if(c == '.'){
                    state = Nachkomma;
                }
                else if(!isdigit(c)){
                    return false;
                }
                break;
            case Vorkomma:            if(!isdigit(c)){
                state = Vorkomma;
            }
            else if (c == '.'){
                state = Nachkomma;
            }
            break;
            case Nachkomma:            if(!isdigit(c)){
                state = Nachkomma;
            }
            else if ((c == 'e') || (c == 'E')){
                state = Exponent;
            }
            break;
            //case Exponent:
            if(c == '-'){
                //        state = Vorzeichen;
                //    }
                //    else if(c != Vorzeichen){
                //        return false;
                //    }
                //    else if (!isdigit(c)){
                //        state = Nachkomma;
                }
                break;
            default:             cout << "Keine gültige eingabe" << endl;
            break;
        }
    }
  
}

Das Programm rechnet auch wie es soll nur erkennt das Programm nicht das (4-e) ein Fehler ist.
Nun, du prüfst nicht alle Fälle.
Der State "Vorzeichen" müsste so korrekt sein (auch wenn man das ein bisschen optimieren könnte).
Der State Vorkomma: Falls es keine Ziffer ist, bleibt der State ja nicht ein Vorkomma? Dann sollte es eigentlich als falsch bezeichnet werden.
("-abc" wäre nach dieser Prüfung eine korrekte Doublezahl).
Den Fall "c == '.'" würde ich daher zuerst bearbeiten, also
C++:
case Vorkomma:
    if(c == '.') state = Nachkomma;
    else if(!isdigit(c)) return false;
    break;
(Kommt hier drauf an, ob "4e10" eine Doublezahl sein soll. Falls ja, dann musst du zusätzlich auf 'e' prüfen).

Der State Nachkomma ist analog zu behandeln (hier ist 'e' / 'E' erwartet für den Statewechsel, sonst, falls nicht isdigit() abbrechen, sonst einfach weitermachen in diesem State).

Den Case Exponent behandelst du in diesem Code ja nicht einmal?
Anyway, in den State "Vorzeichen" zu wechseln ist hier sicher falsch.
Wenn schon, dann müsstest du einen neuen State "ExpVorzeichen" erschaffen, der dann einzig den Exponenten behandelt.
Dein aktueller Code akzeptiert übrigens nur dann eine Eingabe nicht, wenn das erste Zeichen weder '-' noch '.' noch eine Ziffer ist.
Ansonsten wird alles akzeptiert (bzw. der Rückgabewert ist undefiniert).
Du solltest am Ende der Funktion ein "return true" hinzufügen, damit klar ist, dass, falls ein String durch alle States ohne Fehler durchkommt, dieser eine gültige Double-Zahl ist.

Ich habe mir überlegt das so zu machen das ich die Eingabe mit dem Case Vorzeichen und einem Integer vergleiche:
while(Vorzeichen = True and c == b)
do...return false;
Also erstens müsste das
C++:
while(state == Vorzeichen && c == b)
{
   return false,
}
lauten und zweitens verstehe ich nicht, was du damit erreichen willst.

Ich weiss echt nicht, wie ich dir die States noch besser erläutern kann...
Ein einfaches Beispiel vielleicht:
Sagen wir, du baust einen Kühlschrank. Wenn die Türe aufgeht, soll das Licht gradiell angehen, geht die Türe zu, soll das Licht sofort abschalten.

Code:
State: Zu  ====Türe auf====> State: Öffnen  ==Hell_Genug==> State: Offen ==== Türe zu ====> State: Schliessen ===== Türe geschlossen ===> State: Zu
                              |          |
                              |          |
                              heller() =>|
Hier ist der Erste und der Letzte State natürlich derselbe, aber es ist schwierig, das in ASCII so hinzuzeichnen.
Hier hat einzig der State "Öffnen" eine Sonderfunktion: Es wird der Helligkeitswert stetig erhöht, bis dieser hoch genug ist, dann wird in den State "Offen" gewechselt.
Im State "Zu" ist das Licht aus. Im State Offen ist es komplett an. Im State "Öffnen" nimmt es zu.
Im State "Schliessen" bleibt es an, wir bräuchten diesen State für dieses Modell also gar nicht.
Wichtig ist hier aber: Der State "Öffnen" wird niemals bleiben. Nach einer gewissen Zeit wechselt dieser State automatisch zu "Offen". Die anderen States können theoretisch bleiben ("Schliessen" ist ein Spezialfall, da er eigentlich nichts macht).
In deinem Beispiel sind die State-Transitions (hier mit ==== <Name> ==> bezeichnet) die Gründe, warum ein Stateswitch stattfindet. Für dein Beispiel ist es z.B. "State: Vorzeichen === '.' ===> State: Nachkomma".
Deine State Machine muss immer an ein Ende kommen; kam sie ohne Fehler dahin, dann ist die Eingabe korrekt.
Das heisst für dich: Jedes Zeichen, das erwartet wurde (entweder als Signal für einen Statewechsel oder ein Signal für ein beibehalten des State), ist ok. Alle anderen Zeichen sorgen dafür, dass die FSM sofort abbricht und einen Fehler meldet.
Dein aktueller Code tut das nur für den ersten Case ("Vorzeichen"), der Rest hat keine einzige Abbruchbedingung...
Vielleicht half das ja etwas...

Gruss
cwriter