String überprüfen auf Alphabet?



  • Hallo Community,

    wie ist es mir möglich einen C String zu überprüfen ob nur gültige Zeichen [a-z] bzw. [A-Z] oder ÄäÖöÜü eingegeben wurde? Am besten bereits beim scanf()!

    Habe mal was gelesen das man beim

    scanf("%1[a-z]", string);
    

    eingeben kann, jedoch überprüft das nur auf ein Zeichen! Geht soetwas auch mit einem 10 Zeichen langen String? Das wie beschrieben nur aus Buchstaben bestehen darf? 🙂

    Will das möglichst ordentlich lösen aber mir fällt nur ein schlampiger Code ein. 😞

    LG X_2F9



  • int istDeutsch(const char *s)
    { 
      int i=-1;
      sscanf(s,"%*[a-zA-ZäöüÄÖÜß]%n",&i);
      return i==strlen(s);
    }
    

    Encoding deines IDE Editors und der Laufzeitumgebung beachten.



  • Wenn ich richtig verstehe, was du da vorhast:

    scanf("%10[a-z]", string);
    

    Das liest bis zu 10 Zeichen aus der angegebenen Menge von stdin in string ein (exklusive Sentinel, d.h. string braucht Platz für 11 Zeichen, um den Sentinel aufzunehmen).

    Beispiel:

    #include <stdio.h>
    
    int main(void) {
      char buf[21];
    
      scanf("%20[a-z]", buf);
      puts(buf);
    
      return 0;
    }
    

    ->

    $ echo abcdefghijklmnopqrstuvwxyz | ./a.out 
    abcdefghijklmnopqrst
    $ echo abc123abc | ./a.out 
    abc
    


  • So ich hab jetzt meinen Code etwas umgeschrieben, im Kopf funktioniert er einwandfrei. *grins*

    Ich hab aber noch ein kleines Problem mit den Pointern, da ich die normalerweise noch nicht gelernt habe.. will aber versuchen die Musterlösung meiner Lehrkraft zu übertreffen. 😃

    Hier mal mein Code

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    void vok_contain(char [], char [], int &, int &);
    int isGerman(const char *);
    
    int main(){
        char word[11];
        char vokale[11] = {"AaEeIiOoUu"};
        int vok=0, kon=0, prf=0;
        if(isGerman(word)){
            printf("richtig");
        } else printf("Falsch");
        vok_contain(word[11], vokale[11], vok, kon);
        return 0;
    }
    
    int isGerman(const char *s){
      int i=-1;
      sscanf(s,"%*10[a-zA-ZäöüÄÖÜß]%n",&i);
      return i==strlen(s);
    }
    
    void vok_contain(char main[], char search[], int &vok, int &kon){
        for(int i=0;i<strlen(search);i++){
            for(int j=0;j<strlen(main);j++){
                if(main[j]==search[i]){
                    vok++;
                } else kon++;
            }
        }
    }
    

    Jetzt kriege ich aber leider folgende Fehlermeldung beim kompilieren, kann auch sein das es rein logisch nicht funktioniert deshalb wäre ich euch verbunden wenn Ihr kurz drüber schauen könntet 🙂

    ||warning: command line option "-std=c99" is valid for C/ObjC but not for C++|
    X:\FILES\C\Aufgabe 15\d.cpp||In function int main()':| X:\\FILES\\C\\Aufgabe 15\\d.cpp|15|error: invalid conversion from \char' to `char*'|
    X:\FILES\C\Aufgabe 15\d.cpp|15|error: initializing argument 1 of void vok_contain(char*, char*, int&, int&)'| X:\\FILES\\C\\Aufgabe 15\\d.cpp|15|error: invalid conversion from \char' to `char*'|
    X:\FILES\C\Aufgabe 15\d.cpp|15|error: initializing argument 2 of `void vok_contain(char*, char*, int&, int&)'|
    ||=== Build finished: 4 errors, 1 warnings ===|



  • - benenne deine Quelldatei um von .cpp in c
    - "main" ist ein reserviertes Schlüsselwort in C, daher auch den Funktionsparameter umbenennen
    - ein Zeiger wird typ
    deklariert nicht wie du typ& (Dereferenzierung funktioniert dann natürlich auch so nicht)
    - & sieht bei dir nach Referenzen und somit C++ aus, bist du dir sicher, dass du ein C Programm möchtest und keins für C++ ?



  • Das mit C++ bzw Referenzen hab ich nur deshalb gemacht, weil wir das mal kurz behandelt haben.

    Aber ich beachte mal die Punkte die du mir gesagt hast und versuch das mal mit den * hinzubekommen 🙂 Danke schonmal!



  • Gibt es mit sscanf() auch die Möglichkeit, bis zu einem bestimmten Zeichen
    einzulesen?

    Beispiel:

    LOAD myfile.txt AS test

    sscanf(string, "%s%[^AS]%s%s", s1, s2, s3, s4);

    -> so würde man ja es einlesen, aber sollte A oder S in dem String
    vorkommen, so wird es als ungueltig erkannt. ^ heisst ja auch ohne die
    Buchstaben, aber gibt es auch sowas wie "bis"? Es darum, wenn Leerzeichen
    in dem Dateinamen sind - das ich trotzdem den gesamten String habe:

    LOAD myfile 1.txt AS test



  • Naja, was ist, wenn du "AS" im Dateinamen hast?

    Sscanf ist für deinen Anwendungfall mit hoher Wahrscheinlichkeit nicht mächtig genug, und es fällt mir schwer, eine Ausweichmöglichkeit anzubieten, ohne die genaue Sprachspezifikation zu kennen. Sollen Leerzeichen auch im zweiten Token erlaubt sein? Was ist, wenn jemand

    LOAD DATA AS SUPPLIED.TXT AS DATA AS SUPPLIED
    

    eingibt, um mal einen pathologischen Fall zu konstruieren? Was ist, wenn ein Leerzeichen am Anfang eines Dateinamens auftaucht? soll

    LOAD  foo.txt AS foo
    

    sich anders verhalten als

    LOAD foo.txt AS foo
    

    ?

    Die einzige Möglichkeit, die ich sehe, das mit sscanf einigermaßen brauchbar hinzukriegen, ist, die Syntax umzustellen und Leerzeichen im internen Bezeichner zu verbieten, etwa

    #include <stdio.h>
    #include <string.h>
    
    int main(void) {
      char const *input = "LOAD AS test FILE foo bar.txt";
      char a[100], b[100];
    
      sscanf(input, "LOAD AS %s FILE %[^\n]", a, b);
    
      puts(a);
      puts(b);
    
      return 0;
    }
    

    Das löst das Leerzeichenproblem nicht, aber damit könnte man zumindest arbeiten. Du solltest dir allerdings überlegen, ob nicht ein anderer Ansatz sinnvoller wäre - beispielsweise, Anführungszeichen um den Dateinamen zu verlangen und mit regulären Ausdrücken zu arbeiten.



  • Und wenn man die Synatax nicht umstellen kann?
    Welche bessere Möglichkeit würde es geben, das zu analysieren? Nur mit
    sscanf() wird das wohl nicht klappen. (um auch das Leerzeichenproblem zu lösen?)

    Ein solcher Befehler "LOAD foo.txt AS foo" befindet sich u.a. in einer Textdatei. Mit Regex wirds da wohl schwer. Wenn der Dateiname aus Leerzeichen besteht, dann würde ich das mit Anführungszeichen kennzeichnen. Aber dennoch hilft mir sscanf() da noch nicht so recht weiter?!



  • Wenn der hintere Bezeichner kein AS beinhalten darf, kommst du mit Regexes vielleicht hin; da wird üblicherweise greedy gematcht. Mit einem POSIX-konformen Compiler etwa so:

    #include <assert.h>
    #include <stddef.h>
    #include <stdio.h>
    #include <string.h>
    
    #include <regex.h>
    
    void extract_match(char *dest, size_t n, regmatch_t match, char const *str) {
      regoff_t match_len = match.rm_eo - match.rm_so;
      size_t str_len;
    
      if(match_len <= 0) {
        *dest = '\0';
        return;
      }
    
      str_len = n <= match_len ? n - 1 : match_len;
    
      strncpy(dest, str + match.rm_so, str_len);
      dest[str_len] = '\0';
    }
    
    int main(void) {
      char const *input = "LOAD foo bar.txt AS foo";
      regex_t re;
      regmatch_t matches[3];
      char a[100], b[100];
    
      assert(0 == regcomp(&re, "LOAD (.*) AS (.*)", REG_EXTENDED));
      assert(0 == regexec(&re, input, 3, matches, 0));
    
      extract_match(a, 100, matches[1], input);
      extract_match(b, 100, matches[2], input);
    
      regfree(&re);
    
      puts(a);
      puts(b);
    
      return 0;
    }
    

    Im oben genannten pathologischen Fall erhieltest du dann als ersten Token "DATA AS SUPPLIED.TXT AS DATA" und als zweiten "SUPPLIED".

    Das ist aber schon eine etwas dreckige Angelegenheit - hast du keine vernünftige Sprachspezifikation, an die du dich halten kannst? So, wie du das beschreibst, sind deine Eingaben unter Umständen nicht eindeutig.



  • Es soll nach dem ANSI-C-Standard implementiert sein - ich denke, deine beschriebene Methode wird wahrscheinlich nicht gerne gesehen.

    Eindeutig im Sinne der Anordnung sind die Eingaben schon. Also
    die Reihenfolge "LOAD foo bar.txt AS foo" ist fest, was varibel ist, ist foo bar.txt und foo.

    Es gibt praktisch 2 Fälle:

    1. LOAD foobar.txt AS foo
    2. LOAD "foo bar.txt" AS foo
    (Wenn Dateiname Leerzeichen enthält, ist dieser mit Anführungszeichen zu kennzeichnen)



  • Ist das eine Hausaufgabe?



  • Ja, sowas in der Art.



  • Ich wüsste ganz genau, wie ich das Problem mit Regex lösen würde, aber das geht ja leider nicht.

    Dieses "LOAD foobar.txt AS foo" ist ja nur eines der 6 Kommandos, also muss ich mir da eine gute Lösung einfallen lassen.

    Würde es mit strtok() besser gehen? Ist wahrscheinlich auch sehr umfangreich? Was gibt es denn noch für Möglichkeiten, so etwas am besten zu Parsen? Ich bin für alles offen, sscanf() macht mich langsam wahnsinng.



  • Naja, bei Hausaufgaben kann ich dir keine Komplettlösung in die Hand drücken. Zeig mal her, was du schon hast.

    Ansonsten - die Anführungszeichen um Bezeichner mit Leerzeichen ändern die Lage. Sofern keine Anführungszeichen in den Dateinamen auftauchen dürfen (oder du mir andere Teile der Anforderung verschweigst), ist das mit sscanf lösbar; den %[-Spezifikator siehst du ja oben schon erklärt. Kleiner Tip: Man kann sscanf durchaus mehrmals auf den selben String anwenden.



  • Eine Komplettlösung möchte ich ja auch gar nicht. Tipps reichen.

    Meine Funktion zum Einlesen ist schon etwas länger, mit Überprüfungen, Funktionsaufrufen, etc. - daher poste ich mal einen Teil:

    while (fgets(line,fsize,file)) {
    
       if(sscanf(line, "%s %[^AS]%s%s%c", command, filename, path, target, &tmp) == 5) && (tmp == '\n')) {
    
          if( !strcmp(command,"LOAD") && !strcmp(path,"AS") ) {
             /* Hier folgen dann noch Ueberprüfungen etc. */
             ...
          }
       }
       ...
       ...
    }
    

    Das sscanf() bezieht sich auf LOAD foobar.txt AS foo
    So, wie es da steht, ist es noch mächtig falsch. Das [^AS] war eine Notlösung, muss gefixt werden. Sowie die Überprüfung auf '\n' - denn wenn nur eine Zeile in der Datei steht, dann liest er das nicht ein und überspringt dieses sscanf.
    Ich muss halt prüfen, ob nach "LOAD foobar.txt AS foo" wirklich nichts mehr kommt - also sowas wäre eine fehlerhafte Zeile "LOAD foobar.txt AS foo FOOBAR".

    Nein, andere Teile verschweige ich nicht. Die Anforderung ist ganz klar: Enthalten Dateinamen Leerzeichen, so müssen sie mit Anführungszeichen umschlossen sein.



  • int split(const char *s,char ***a)
    {
      int r=0,n=0,x=0;
      char *tmp1=malloc(strlen(s)+1);
      while( 1==sscanf(s+=n,"%[^\"]%n",tmp1,&n) )
      {
        if( s[n]=='\"' && (n++, x++&1) )
        {
          *a=realloc(*a,++r*sizeof*a);
          (*a)[r-1]=malloc(strlen(tmp1)+1);
          strcpy((*a)[r-1],tmp1);
        }
        else
        {
          char *tmp2=tmp1, *tmp3=malloc(strlen(tmp1)+1);
          int m=0;
          while( 1==sscanf(tmp2+=m,"%s%n",tmp3,&m) )
          {
            *a=realloc(*a,++r*sizeof*a);
            (*a)[r-1]=malloc(strlen(tmp3)+1);
            strcpy((*a)[r-1],tmp3);
          }
          free(tmp3);
        }
      }
      return free(tmp1),r;
    }
    
    while (fgets(line,fsize,file)) {
      char **a=0;
      int r=split(line,&a);
      if( r==4 && !strcmp(a[0],"LOAD") && !strcmp(a[2],"AS" )
        printf("\n%s:%s:%s:%s",a[0],a[1],a[2],a[3]);
      else
        ...
      while( r-- ) free(a[r]);
      free(a);
    }
    

    Funktioniert auch für mehrere \"-geklammerte Werte an beliebiger Position wie
    LOAD "foo bar.txt" AS "foo bar"



  • Jaaaa...wie dem auch sei, ich bevorzuge einen etwas einfacheren Ansatz. Denkbar etwa

    #include <stddef.h>
    #include <stdio.h>
    #include <string.h>
    
    int parse(char const *input, char *buf1, char *buf2)
    {
      if(sscanf(input, "LOAD \"%[^\"]\" AS %s", buf1, buf2) == 2 ||
         sscanf(input, "LOAD %s AS %s"        , buf1, buf2) == 2) {
        return 0;
      }
    
      return -1;
    }
    
    int main(void) {
      char a[100], b[100];
    
      if(!parse("LOAD foo AS bar", a, b)) {
        puts(a);
        puts(b);
      }
    
      if(!parse("LOAD \"foo bar\" AS bar", a, b)) {
        puts(a);
        puts(b);
      }
    
      return 0;
    }
    

    Die Prüfung, ob die ganze Zeile gematcht wurde und (wichtig!) dass in buf1 und buf2 genug Speicher vorhanden ist, bleibt dir überlassen. Es ist einfach genug einzupflegen, dass du es schon selbst hinkriegst, und ich kann ruhig schlafen, dass du dich mit dem Code, den du benutzt, auch auseinandersetzen musstest. 😉



  • Die von Wutz gepostete Lösung, sprich die Funktion split(), verstehe ich nicht ganz. Was die Funktion macht und zurückgibt, ist mir klar, nur folgende Abschnitte sind mir etwas fremd (da noch nie gesehen):

    while( 1==sscanf(s+=n,"%[^\"]%n",tmp1,&n) )
    {
    if( s[n]=='\"' && (n++, x++&1) )
    {
    ...
    }
    else
    {
    ...
    while( 1==sscanf(tmp2+=m,"%s%n",tmp3,&m) )
    ...

    Mir geht es nicht darum, etwas "abzuschreiben", sondern zu lernen und zu verstehen, da ich eine solche Form von sscanf() noch nicht gesehen habe (s+=n).



  • while( 1==sscanf(s+=n,"%[^\"]%n",tmp1,&n) )
    

    %[^\"] matcht einen String aller Zeichen außer ", liest also alle Zeichen bis zum nächsten " ein. %n danach weist die Länge des gematchten Strings der zugeordneten Variable (in diesem Fall n) zu.

    if( s[n]=='\"' && (n++, x++&1) )
    

    Die Bedingung, die geprüft wird, ist "wenn s[n] ein Anführungszeichen und x ungerade ist". Als Nebeneffekt werden n und x erhöht, wenn s[n] ein Anführungszeichen ist. s[n] ist das Zeichen nach dem gerade eingelesenen Token im Originalstring (s wird jeweils vor dem Einlesen auf den Anfang des nächsten Tokens adjustiert), und x (bzw. sein letztes Bit) ist als boolscher Wert zu verstehen, der angibt, ob man sich gerade innerhalb eines String-Literals befindet oder nicht. Ggf. wird der gerade ausgelesene Token weiter auseinandergenommen. s[n] ist immer ein Anführungszeichen, außer am Ende des letzten Tokens, falls dieser nicht in Anführungszeichen steht - es wird hier ja nach Anführungszeichen gesplittet.

    while( 1==sscanf(tmp2+=m,"%s%n",tmp3,&m) )
    

    Vergleichbar zum ersten Codestück, nur wird nicht nach ", sondern nach Whitespaces gesplittet (%s halt).

    Dazu ist anzumerken, dass Code der Form

    p = realloc(p, new_size);
    

    keine gute Idee ist - wenn die Allokation mal daneben gehen sollte, verlierst du auf die Art eh schon kargen Speicher. Das wäre hier im Forum kein großes Problem, wenn der Code dazu geeignet wäre, ein Konzept zu verdeutlichen - man schriebe es in eine Anmerkung und überließe es dem Benutzer, die Fehlerbehandlung gemäß seinen Wünschen zu implementieren, aber bei Wutzs Code handelt es sich um das, was die Jargon-File write-only code nennt. Ich kann dir nur dringendst ans Herz legen, dir einen übersichtlichen Programmierstil anzugewöhnen - Code, in dem jede zweite Zeile sich auf Nebeneffekte verlässt, die man beim Lesen leicht übersieht, haut dir jeder Arbeitgeber, der etwas von Technik versteht und mal auf den Code seiner Angestellten kuckt, um die Ohren. Sogar derjenige, der ihn ursprünglich geschrieben hat, wird zwei Jahre später davor sitzen und sich fragen, was er sich dabei nur gedacht hat.

    In der Kürze liegt Würze, aber es geht dabei um strukturelle Kürze, nicht darum, Zeilenumbrüche zu sparen. Es ist wichtig zu verstehen, dass es bei der Programmierung in erster Linie nicht darum geht, eine Maschine dazu zu überreden, etwas zu tun, sondern um Organisation - dein Job ist es, ein komplexes Problem in weniger komplexe Probleme zu zerlegen, um diese einzeln zu lösen. Es ist in der Regel weder hilfreich, mehrere einfache Probleme in ein komplexeres Problem zusammenzufassen, um sie zusammen zu lösen, noch die Verständlichkeit der Lösung dadurch zu senken, dass man sie mit bedeutungslosen Bezeichnern betreibt. Wenn ich

    struct foo {
      struct foo *p, *q;
      int x;
    };
    
    struct foo *bar(struct foo *s, int x) {
      struct foo *q = malloc(sizeof(struct foo));
    
      return q->x = x, q->q = 0, q->p = s, s->q = q;
    }
    

    schreibe, musst du erstmal nachdenken, worauf ich damit hinaus will. Schreibe ich dagegen

    struct list {
      struct list *next;
      struct list *prev;
      int value;
    };
    
    struct list *list_push_front(struct list *head, int value) {
      struct list *new_head = malloc(sizeof(struct list));
    
      new_head->value = value;
      new_head->prev  = 0;
      new_head->next  = head;
      head    ->prev  = new_head;
    
      return new_head;
    }
    

    ist es sofort verständlich. Variablennamen wie tmp1, tmp2 und tmp3 sind nicht hilfreich.

    Außerdem betreibt man die Allokation Arrays variabler Länge üblicherweise blockweise, um nicht andauernd an den relativ teuren Heap gehen zu müssen, zumal das diesen auf Dauer fragmentiert. Besonders in nebenläufigen Applikationen kann es ein echtes Performanceproblem werden, wenn sich mehrere Threads dauernd um den Heap streiten müssen.


Log in to reply