繁体   English   中英

删除逗号之间的白色字符,但不删除逗号内的内容

[英]Remove white chars between commas, but not between what inside the commas

我是 C 新手,正在学习 C90。 我正在尝试将字符串解析为命令,但我很难尝试删除白色字符。

我的目标是解析这样的字符串:

NA ME, NAME   , 123 456, 124   , 14134, 134. 134   ,   1   

进入这个:

NA ME,NAME,123 456,124,14134,134. 134,1

所以参数中的白色字符仍然存在,但其他白色字符被删除。

我想过使用strtok,但我仍然想保留逗号,即使有多个连续的逗号。

直到现在我使用:

void removeWhiteChars(char *s)
{
    int i = 0;
    int count = 0;
    int inNum = 0;
    while (s[i])
    {
        if (isdigit(s[i]))
        {
            inNum = 1;
        }
        if (s[i] == ',')
        {
            inNum = 0;
        }
        if (!isspace(s[i]) && !inNum)
            s[count++] = s[i];
        else if (inNum)
        {
            s[count++] = s[i];
        }

        ++i;
    }
    s[count] = '\0'; /* adding NULL-terminate to the string */
}

但是它只跳过数字并且不会删除数字后直到逗号的白色字符,这是非常错误的。

我会很感激任何帮助,我已经坚持了两天了。

每当遇到可能的可跳过空白时,您都需要进行前瞻。 下面的函数,每次看到空格时,都会向前检查它是否以逗号结尾。 同样,对于每个逗号,它都会检查并删除所有后续空格。

// Remove elements str[index] to str[index+len] in place
void splice (char * str, int index, int len) {
  while (str[index+len]) {
    str[index] = str[index+len];
    index++;
  }
  str[index] = 0;
}

void removeWhiteChars (char * str) {
  int index=0, seq_len;

  while (str[index]) {
    if (str[index] == ' ') {
      seq_len = 0;

      while (str[index+seq_len] == ' ') seq_len++;

      if (str[index+seq_len] == ',') {
        splice(str, index, seq_len);
      }
    }
    if (str[index] == ',') {
      seq_len = 0;
      while (str[index+seq_len+1] == ' ') seq_len++;

      if (seq_len) {
        splice(str, index+1, seq_len);
      }
    }
    index++;
  }
}

解决任何解析问题的一种简短而可靠的方法是使用状态循环,它只不过是对原始字符串中所有字符的循环,您使用一个(或多个)标志变量来跟踪任何状态你需要跟踪。 在您的情况下,您需要知道您是否正在阅读逗号后(之后)的状态。

这控制你如何处理下一个字符。 您将使用一个简单的计数器变量来跟踪您已读取的空格数,并且当您遇到下一个字符时,如果您不是逗号后,则将该空格数附加到您的新字符串中。 如果你是逗号后,你丢弃缓冲的空间。 (您可以使用遇到','本身作为不需要保存在变量中的标志)。

要删除','分隔符周围的空格,您可以编写一个rmdelimws()函数,该函数将要填充的新字符串和要从中复制的旧字符串作为参数,并执行类似以下操作:

void rmdelimws (char *newstr, const char *old)
{
  size_t spcount = 0;               /* space count */
  int postcomma = 0;                /* post comma flag */
  
  while (*old) {                    /* loop each char in old */
    if (isspace (*old)) {           /* if space? */
      spcount += 1;                 /* increment space count */
    }
    else if (*old == ',') {         /* if comma? */
      *newstr++ = ',';              /* write to new string */
      spcount = 0;                  /* reset space count */
      postcomma = 1;                /* set post comma flag true */
    }
    else {                          /* normal char? */
      if (!postcomma) {             /* if not 1st char after comma */
        while (spcount--) {         /* append spcount spaces to newstr */
          *newstr++ = ' ';
        }
      }
      spcount = postcomma = 0;      /* reset spcount and postcomma */
      *newstr++ = *old;             /* copy char from old to newstr */
    }
    old++;                          /* increment pointer */
  }
  *newstr = 0;                      /* nul-terminate newstr */
}

注意:如果newstr没有被初始化为全零,则更新为肯定地终止,如下所示)

如果要保存行中的尾随空格(例如,示例中结尾1之后的空格),可以在上面的字符串以 nul 结尾之前添加以下内容:

  if (!postcomma) {                 /* if tailing whitespace wanted */
    while (spcount--) {             /* append spcount spaces to newstr */
      *newstr++ = ' ';
    }
  }

把它放在一起是一个简短的例子:

#include <stdio.h>
#include <ctype.h>

void rmdelimws (char *newstr, const char *old)
{
  size_t spcount = 0;               /* space count */
  int postcomma = 0;                /* post comma flag */
  
  while (*old) {                    /* loop each char in old */
    if (isspace (*old)) {           /* if space? */
      spcount += 1;                 /* increment space count */
    }
    else if (*old == ',') {         /* if comma? */
      *newstr++ = ',';              /* write to new string */
      spcount = 0;                  /* reset space count */
      postcomma = 1;                /* set post comma flag true */
    }
    else {                          /* normal char? */
      if (!postcomma) {             /* if not 1st char after comma */
        while (spcount--) {         /* append spcount spaces to newstr */
          *newstr++ = ' ';
        }
      }
      spcount = postcomma = 0;      /* reset spcount and postcomma */
      *newstr++ = *old;             /* copy char from old to newstr */
    }
    old++;                          /* increment pointer */
  }
  *newstr = 0;                      /* nul-terminate newstr */
}


int main (void) {
  
  char str[] = "NA ME, NAME   , 123 456, 124   , 14134, 134. 134   ,   1   ",
       newstr[sizeof str] = "";
  
  rmdelimws (newstr, str);
  
  printf ("\"%s\"\n\"%s\"\n", str, newstr);
}

示例使用/输出

$ ./bin/rmdelimws
"NA ME, NAME   , 123 456, 124   , 14134, 134. 134   ,   1   "
"NA ME,NAME,123 456,124,14134,134. 134,1"

下面的作品,至少对于您的输入字符串。 我绝对没有声称它的效率或优雅。 我没有尝试就地修改s ,而是写入了一个新字符串。 我遵循的算法是:

  • startPos初始化为 0。
  • 循环s直到找到一个逗号。
  • 从该位置备份,直到找到第一个非空格字符。
  • memcpystartPos到那个位置到一个新的字符串。
  • 在新字符串的下一个位置添加一个逗号。
  • 从逗号位置向前看,直到找到第一个非空格字符,将其设置为startPos
  • 冲洗并重复
  • 最后,用strcat附加最终标记
void removeWhiteChars(char *s)
{
    size_t i = 0;
    size_t len = strlen(s);
    char* newS = calloc(1, len);
    size_t newSIndex = 0;
    size_t startPos = 0;

    while (i<len)
    {
        // find the comma
        if (s[i] == ',')
        {            
            // find the first nonspace char before the comma
            ssize_t before = i-1;
            while (isspace(s[before]))
            {
                before--;
            }
            
            // copy from startPos to before into our new string
            size_t amountToCopy = (before-startPos)+1;
            memcpy(newS+newSIndex, s+startPos, amountToCopy);
            newSIndex += amountToCopy;
            newS[newSIndex++] = ',';

            // update startPos
            startPos = i+1;
            while (isspace(s[startPos]))
            {
                startPos++;
            }
            
            // update i
            i = startPos+1;
        }
        else
        {
            i++;
        }
    }

    // finally tack on the end
    strcat(newS, s+startPos);

    // You can return newS if you're allowed to change your function
    // signature, or strcpy it to s
    printf("%s\n", newS);    
}

我也只用你的输入字符串测试过它,它可能会在其他情况下中断。

示范

您可以使用状态机在O(n)中就地修改它。 在这个例子中,我使用re2c为我设置和保持状态。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

static void lex(char *cursor) {
    char *out = cursor, *open = cursor, *close = 0;
start:
    /*!re2c /* Use "re2c parse.re.c -o parse.c" to get C output file. */
    re2c:define:YYCTYPE = "char";
    re2c:define:YYCURSOR = "cursor";
    re2c:yyfill:enable = 0;
    /* Whitespace. */
    [ \f\n\r\t\v]+ { if(!close) open = cursor; goto start; }
    /* Words. */
    [^, \f\n\r\t\v\x00]+ { close = cursor; goto start; }
    /* Comma: write [open, close) and reset. */
    "," {
        if(close)
            memmove(out, open, close - open), out += close - open, close = 0;
        *(out++) = ',';
        open = cursor;
        goto start;
    }
    /* End of string: write any [open, close). */
    "\x00" {
        if(close)
            memmove(out, open, close - open), out += close - open;
        *(out++) = '\0';
        return;
    }
    */
}

int main(void) {
    char command[]
        = "NA ME, NAME   , 123 456, 124   , 14134, 134. 134   ,   1   ";
    printf("<%s>\n", command);
    lex(command);
    printf("<%s>\n", command);
    return EXIT_SUCCESS;
}

这可以通过懒惰来起作用; 也就是说,改变字符串的写法,直到我们可以确定它是完整的,无论是在逗号还是在字符串的末尾。 它很简单,属于常规语言,没有前瞻。 它保留了它们之间没有逗号的单词之间的空格。 它还会覆盖字符串,因此不会使用额外的空间; 我们可以这样做,因为编辑只涉及删除。

请试试这个:

void removeWhiteChars(char *s)
{
    int i = 0;
    int count = 0;
    int isSomething = 0;
    while (s[i])
    {
        if (s[i] == ',' && isSomething == 0)
            isSomething = 2;
        else if (s[i] == ',' && isSomething == 1)
            isSomething = 2;
        else if (s[i] == ',' && isSomething == 2)
        {
            s[count++] = ',';
            s[count++] = s[i];
            isSomething = 0;
        }
        else if (isspace(s[i]) && isSomething == 0)
            isSomething = 1;
        else if (isspace(s[i]) && isSomething == 1)
            isSomething = 1;
        else if (isspace(s[i]) && isSomething == 2)
            isSomething = 2;
        else if (isSomething == 1)
        {
            s[count++] = ' ';
            s[count++] = s[i];
            isSomething = 0;
        }
        else if (isSomething == 2)
        {
            s[count++] = ',';
            s[count++] = s[i];
            isSomething = 0;
        }
        else
            s[count++] = s[i];

        ++i;
    }
    s[count] = '\0'; /* adding NULL-terminate to the string */
}

这是一种可能的算法。 它不一定像这里介绍的那样经过优化,但存在是为了演示一种可能的算法实现。 它是故意部分抽象的。

以下是一个非常健壮的 O(n) 时间算法,您可以使用它来修剪空白(如果您对其进行概括和扩展,还可以进行其他操作)。

但是,此实现尚未经过验证可以按原样工作。

您应该跟踪前一个字符和相关空格,以便如果您看到{ ',', ' ' }{ CHAR_IN_ALPHABET, ' '} ,您将开始一个链和一个表示当前执行路径的值。 当您看到任何其他字符时,如果检测到第一个序列,则链应该中断,反之亦然。 我们将定义一个函数:

// const char *const in: indicates intent to read from in only
void trim_whitespace(const char *const in, char *out, uint64_t const out_length);

我们正在定义一个明确的算法,其中所有执行路径都是已知的,因此对于每个唯一可能的执行状态,您应该使用函数中定义的枚举和 switch 语句(除非 goto 和标签更好地模拟算法的行为):

void trim_whitespace(const char *const in, char *out, uint64_t const out_length) {
    // better to use ifdefs first or avoid altogether with auto const variable,
    // but you get the point here without all that boilerplate
    #define CHAR_NULL 0

    enum {
        DEFAULT = 0,
        WHITESPACE_CHAIN
    } execution_state = DEFAULT;
    
    // track if loop is executing; makes the logic more readable;
    // can also detect environment instability
    // volatile: don't want this to be optimized out of existence
    volatile bool executing = true;

    while(executing) {
        switch(execution_state) {
        case DEFAULT:
            ...
        case WHITESPACE_CHAIN:
            ...
        default:
            ...
        }
    }

    function_exit:
        return;

    // don't forget to undefine once finished so another function can use
    // the same macro name!
    #undef CHAR_NULL
}

可能执行状态的数量等于2**ceil(log_2(n))其中n是与当前算法的操作相关的实际执行状态的数量。 您应该明确命名它们并在 switch 语句中为它们设置大小写。

DEFAULT情况下,我们只检查逗号和“合法”字符。 如果前一个字符是逗号或合法字符,而当前字符是空格,那么我们要将状态设置为WHITESPACE_CHAIN

WHITESPACE_CHAIN案例中,我们测试是否可以根据我们开始的字符是逗号还是合法字符来修剪当前链。 如果当前字符可以被修剪,它会被简单地跳过,然后我们进入下一次迭代,直到我们根据我们正在寻找的内容找到另一个逗号或合法字符,然后将执行状态设置为DEFAULT 如果我们确定这个链不可修剪,那么我们添加我们跳过的所有字符并将执行状态设置回DEFAULT

循环应该是这样的:

...
// black boxing subjectives for portability, maintenance, and readability
bool is_whitespace(char);
bool is_comma(char);
// true if the character is allowed in the current context
bool is_legal_char(char);
...

volatile bool executing = true;

// previous character (only updated at loop start, line #LL)
char previous = CHAR_NULL;
// current character (only updated at loop start, line #LL)
char current = CHAR_NULL;
// writes to out if true at end of current iteration; doesn't write otherwise
bool write = false;
// COMMA: the start was a comma/delimeter
// CHAR_IN_ALPHABET: the start was a character in the current context's input alphabet
enum { COMMA=0, CHAR_IN_ALPHABET } comma_or_char = COMMA;

// current character index (only updated at loop end, line #LL)
uint64_t i = 0, j = 0;

while(executing) {
    previous = current;
    current = in[i];

    if (!current) {
        executing = false;
        break;
    }

    switch(execution_state) {
        case DEFAULT:
            if (is_comma(previous) && is_whitespace(current)) {
                execution_state = WHITESPACE_CHAIN;
                write = false;
                comma_or_char = COMMA;
            } else if (is_whitespace(current) && is_legal_char(previous)) { // whitespace check first for short circuiting
                execution_state = WHITESPACE_CHAIN;
                write = false;
                comma_or_char = CHAR_IN_ALPHABET;
            }
            
            break;

        case WHITESPACE_CHAIN:
            switch(comma_or_char) {
                case COMMA:
                    if (is_whitespace(previous) && is_legal_char(current)) {
                        execution_state = DEFAULT;
                        write = true;
                    } else if (is_whitespace(previous) && is_comma(current)) {
                        execution_state = DEFAULT;
                        write = true;
                    } else {
                        // illegal condition: logic error, unstable environment, or SEU
                        executing = true;
                        out = NULL;
                        goto function_exit;
                    }

                    break;

                case CHAR_IN_ALPHABET:
                    if (is_whitespace(previous) && is_comma(current) {
                        execution_state = DEFAULT;
                        write = true;
                    } else if (is_whitespace(previous) && is_legal_char(current)) {
                        // abort: within valid input string/token
                        execution_state = DEFAULT;
                        write = true;
                        // make sure to write all the elements we skipped; 
                        // function should update the value of j when finished
                        write_skipped(in, out, &i, &j);
                    } else {
                        // illegal condition: logic error, unstable environment, or SEU
                        executing = true;
                        out = NULL;
                        goto function_exit;
                    }

                    break;

                default:
                    // impossible condition: unstable environment or SEU
                    executing = true;
                    out = NULL;
                    goto function_exit;
            }
            
            break;

        default:
            // impossible condition: unstable environment or SEU
            executing = true;
            out = NULL;
            goto function_exit;
    }

    if (write) {
        out[j] = current;
        ++j;
    }

    ++i;
}

if (executing) {
    // memory error: unstable environment or SEU
    out = NULL;
} else {
    // execution successful
    goto function_exit;
}

// end of function

还请使用空格这个词来描述这些字符,因为这是它们通常所说的,而不是“白色字符”。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM