Control characters effizient von einem char array entfernen



  • Hallo,

    ich habe ein char array, der folgenden Inhalt enthalten kann:

    // -0.5 0.5 0.5 \n\t-0.5 -0.5 0.5\n\t\t

    const char text[] = {"-0.5 0.5 0.5   \n\t-0.5 -0.5 0.5\n\t\t"};
    

    Dieses Array soll von \n \t usw. und von mehrmaligen whitespaces entfernt werden.
    Ein sauberes Array soll enstehen:
    [-0.5 0.5 0.5 -0.5 -0.5 0.5 0.5 0.5 -0.5]

    Ich habe auf die schnelle eine Methode geschrieben die dies ermöglicht.
    Gibt es Verbesserungen bzw. eine schnellere Variante?
    Danke!

    void removeControlCharacterSetFromCharArray(char* charPtr)
    {
       char *ptr = charPtr;
       int size = strlen(currentPtr);
       int i = 0;
    
       while (ptr[i] != NULL)
       {
          if (ptr[i] == 0x20 || (ptr[i] >= 0x9 && ptr[i] <= 0xd))
          {
             if (ptr[i+1] == 0x20 || (ptr[i+1] >= 0x9 && ptr[i+1] <= 0xd) && (ptr[i+1] != NULL))
             {
                memmove(&ptr[i], &ptr[i+1], size-i);
                continue;
             } else
                ptr[i] = 0x20;
           }
           i++;
       }
    }
    

    Edit: Leerzeichen zwischen den Zahlen sollen bleiben.



  • Warum kopierst du charPtr?
    strlen gibt size_t zurück, nicht int.
    Warum keine for-Schleife?
    Für diese Abfrage gibt es die Funktion isspace() aus <ctype.h>
    Wenn du gleich zählst wie viele spaces aufeinander folgen sparst du dir uU einige memmoves.
    Ein Kopieren des Arrays könnte uU schneller sein, ob sich das lohnt kommt aber wohl auf die erwartete Größe des Arrays an. (Bei 500MB ist kopieren etwas kritisch. ;))
    Eigentlich brauchst du hier keine Laufvariable denke ich mal, du könntest auch einfach ptr inkrementieren. Könnte eventuell auch etwas schneller sein. :xmas1:



  • strpbrk (string.h) ist dein Freund



  • Statt hintere Zeichen mehrfach um nur jeweils eine Stelle nachrücken zu lassen, kannst du das auch alles in einem Rutsch erledigen -- also in einer Schleife, statt zwei (wenn man memmove als Schleife zählt).

    Also, im Wesentlichen kannst du removeControlCharacterSetFromCharArray so ähnlich wie std::remove_if aus C++ implementieren. Siehe

    #include <string.h>
    #include <stdio.h>
    #include <algorithm>
    
    int main()
    {
      char msg[] = "Hallo,\nWelt!\n";
      *std::remove_if(msg,msg+strlen(msg),[](char c){
        return (c=='\n');
      }) = 0;
      printf("%s\n",msg);
    }
    

    Wie remove_if aussieht, steht hier. In diesem Fall stünde ForwardIter für char* und pred für den logischen Ausdruck, der sagt, was gelöscht werden soll. Die Dereferenzierung und Zuweisung schreibt den neuen Nullterminator.



  • Dir ist schon klar, das es hier um plain C geht?



  • Mal eine spontane Idee:

    void removeControlCharacterSetFromCharArray(char *input)
    {
    	int len = strlen(input);
    	int i;
    
    	if (len == 0)
    		return;
    
    	for (i = 0; i < len && input[i]; ++i)
    	{
    		int c;
    		for (c = 0; isspace(input[i + c]); ++c)
    			input[i + c] = ' ';
    
    		if (c > 1)
    			strncpy(&input[i + 1], &input[i + c], len - i);
    	}
    
    	input[i - 1] = 0;
    }
    

    Ohne Garantie.



  • unsigned char removeControlCharacterSetFromCharArray(char *input) 
    {
    	if (!input)
    		return 0;
    
    	size_t i;
    	for (i = 0; i < strlen(input) - 1; i++) {
    		if ((input[i] == '\n') || (input[i] == '\t') || ((input[i] == 0x20) && (input[i+1] == 0x20))) {
    			input[i] = 0x20;
    		}
    	}
    
    	if ((input[i] == '\n') || (input[i] == '\t') || (input[i] == 0x20)) {
    		input[i] = 0;
    	}
    
    	return 1;
    }
    

    Code schnell ausm Kopf heraus, aber sollte alles klar sein, was gemeint ist.

    Ich gehe mal davon aus, dass du zwischen jedem x.x ein Leerzeichen haben willst. Deine Fragestellung ist etwas konfus.



  • functi0n schrieb:

    unsigned char removeControlCharacterSetFromCharArray(char *input) 
    {
    	if (!input)
    		return 0;
    
    	size_t i;
    	for (i = 0; i < strlen(input) - 1; i++) {
    		if ((input[i] == '\n') || (input[i] == '\t') || ((input[i] == 0x20) && (input[i+1] == 0x20))) {
    			input[i] = 0x20;
    		}
    	}
    
    	if ((input[i] == '\n') || (input[i] == '\t') || (input[i] == 0x20)) {
    		input[i] = 0;
    	}
    
    	return 1;
    }
    

    Code schnell ausm Kopf heraus, aber sollte alles klar sein, was gemeint ist.

    Ich gehe mal davon aus, dass du zwischen jedem x.x ein Leerzeichen haben willst. Deine Fragestellung ist etwas konfus.

    Dein Code stellt aber nicht sicher, dass zwischen jeder Zahl nur genau ein Leerzeichen ist. Und sowas wie 0x20 ist einfach nur schlechter Stil.



  • @functi0n
    Die for-Schleife

    for (i = 0; i < strlen(input) - 1; i++)
    

    sollte nie so implementiert werden. Das strlen unbedingt aus dem Schleifen Kopf nehmen. Der Compiler kann das nicht optimieren und damit schafft man es ganz leicht aus O(n) Algorithmen O(n^2) Algorithmen zu basteln.



  • noergel schrieb:

    Dir ist schon klar, das es hier um plain C geht?

    Sicher. Dir ist schon klar, dass der Algorithmus, der in std::remove_if drin steckt in ähnlicher Form in C für die Lösung dieses Problems implementierbar ist?



  • krümelkacker schrieb:

    noergel schrieb:

    Dir ist schon klar, das es hier um plain C geht?

    Sicher. Dir ist schon klar, dass der Algorithmus, der in std::remove_if drin steckt in ähnlicher Form in C für die Lösung dieses Problems implementierbar ist?

    Auch deine Variante berücksichtigt nicht, dass eben genau ein Leerzeichen bleiben muss.

    Eine Lösung in C++ wäre daher vielleicht:

    char text[] = {"-0.1 0.2 0.5   \n\t-0.5 -0.5 0.5\n\t\t"};
    
    	std::stringstream strm(text);
    
    	std::stringstream result;
    
    	float tmp;
    	while (strm >> tmp)
    	{
    		result << tmp << " ";
    	}
    
    	std::cout << "'" << result.str().substr(0, result.str().size() - 1) << "'" << std::endl;
    

    Ausgabe: '-0.1 0.2 0.5 -0.5 -0.5 0.5'

    Schau dir einfach mal meine spontane Idee weiter oben an. Die erfüllt die Kriterien, denke ich.



  • Danke für die Antworten.

    Übrigens, wie von manchen hier schon bemerkt, es sollen Leerzeichen zwischen den "Zahlen" bleiben.

    Das Array soll später in ein float array umgewandelt werden.



  • x1c3 schrieb:

    Das Array soll später in ein float array umgewandelt werden.

    Und warum dann der Aufwand?
    Sowohl die scanf-Familie als auch strod überlesen führende Whitespace (" \n\t\r\v\f").
    Und durch den **endptr bei strtod bekommst du auch die nächste Position.



  • DirkB schrieb:

    x1c3 schrieb:

    Das Array soll später in ein float array umgewandelt werden.

    Und warum dann der Aufwand?
    Sowohl die scanf-Familie als auch strod überlesen führende Whitespace (" \n\t\r\v\f").
    Und durch den **endptr bei strtod bekommst du auch die nächste Position.

    Mit strtod ist das ganze natürlich viel komfortabler! Danke

    Übrigens, den Text erhalte ich von einem XML-Parser und dieser enthält Control-Characters.



  • [quote="fghfgh
    Dein Code stellt aber nicht sicher, dass zwischen jeder Zahl nur genau ein Leerzeichen ist. Und sowas wie 0x20 ist einfach nur schlechter Stil.[/quote]

    Steht irgendwo, dass ich das behauptet habe? Genau lesen, bitte!
    Und diese Funktion spiegelt auch nicht meinen Stil wieder. 😉



  • x1c3 schrieb:

    const char text[] = {"-0.5 0.5 0.5   \n\t-0.5 -0.5 0.5\n\t\t"};
    
    void trim(char *s)
    {
      int n;
      char *z=s;
      while( 1==sscanf(s,"%s%n",z,&n) )
      {
        s+=n;
        (z+=strlen(z)+1)[-1]=' ';
      }
      z!=s?*--z=0:0;
    }
    

    sollte laufen für nicht konstante Strings.

    x1c3 schrieb:

    Das Array soll später in ein float array umgewandelt werden.

    Tja, aber deswegen muss man den String nicht säubern,

    sscanf(text,"%f%f%f%f%f%f",...)
    

    läuft sowohl für

    "-0.5 0.5 0.5   \n\t-0.5 -0.5 0.5\n\t\t"
    

    wie auch für

    "-0.5 0.5 0.5 -0.5 -0.5 0.5"
    

    gleichermaßen.


Anmelden zum Antworten