【问题标题】:Parsing column-based ASCII data in C在 C 中解析基于列的 ASCII 数据
【发布时间】:2012-05-15 13:01:00
【问题描述】:

我正在尝试解析包含固定列数的整数的文本。例如,我的输入文件可能如下所示:

=1=2=3
=4=5=6
=8=910

= 符号表示输入中的空格。 等号不在输入文件中;我只是为了说明的目的把它放在那里。每个整数都包含在没有零填充的两列中,所以第三行不是错字:它是 8、9 和 10。

标准sscanf 不起作用,因为它先消除空格,然后再应用格式字符串。例如,我试过:

sscanf(buf, "%2d%2d%2d", &int1, &int2, &int3)

但它最终将第三行解析为 8、91 和 0。

有没有办法做到这一点,而无需手动逐列提取数据?

【问题讨论】:

  • 我正在与之交互的系统正在生成的自定义格式。不幸的是,无法控制它:/
  • 你不应该假设你有 3 个号码和"%2d%2d%2d"
  • 如果你不介意我说,那是一种糟糕的格式。我认为您将不得不逐列进行操作。在执行 sscanf() 之前,您是否尝试过将所有 '=' 替换为 '0'?
  • 我编辑了问题以澄清等号实际上不在输入中。对此感到抱歉。
  • @madper:输入保证有三个整数。另外,我检查了 scanf 的结果,以确保我读取了正确数量的输入。

标签: c string parsing


【解决方案1】:

你可以一个字一个字地做:

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

int main(void) {
  int val;
  char input[] = "=8=910";
  char *p = input;

  while (*p) {
    val = 0;
    if (isdigit((unsigned char)*p)) val = *p - '0'; // 1st digit
    p++;
    val *= 10;                                      // 2nd
    val += *p++ - '0';                              // digit
    printf("val: %d\n", val);
  }
  return 0;
}

【讨论】:

  • 你是否用%d重新实现sscanf
  • 一次只计算两位数的值,而不是多次复制或读取同一个数字。
  • @Shahbaz:不,他将“使用前导空格”的行为排除在外。
  • 您的解决方案非常有限且不可扩展。如果列大小从 2 变为 3 会怎样?如果在输入中给出列大小怎么办?如果文件中的数字更改为十六进制怎么办?如果这是可配置的呢?你在重新发明轮子,这不是一个好的建议。
  • 你可以很容易地把上面的程序转换成一个函数,它做的事情基本相同,并且有你喜欢的参数:列宽,接受的字符集,错误函数,转换函数,...,。 ..
【解决方案2】:

您可以通过多种不同的方式做到这一点。使用sscanf(或strtol),你有(至少)这两个选项:

  • 复制该列,然后sscanf它:

    char temp[COL_SIZE+1] = {'\0'};  /* last character will always be NUL */
    for (i = 0; i < col_count; ++i)
    {
        memcpy(temp, buf + i * COL_SIZE, COL_SIZE * sizeof(*buf)); /* "* sizeof" actually unnecessary */
        sscanf(temp, "%d", &num[i]); /* or use strtol */
    }
    
  • 您也可以更高效地执行此操作,因为您不会在常量字符串上执行此操作特别是永远不会在字符串文字上执行此操作

    for (i = 0; i < col_count; ++i)
    {
        char temp;
        int column_beg = i * COL_SIZE;
        int column_end = column_beg + COL_SIZE;
        temp = buf[column_end];
        buf[column_end] = '\0';
        sscanf(buf + column_beg, "%d", &num[i]); /* or use strtol */
        buf[column_end] = temp;
    }
    

    它的作用是在每列之后插入一个'\0',读取它,然后恢复原始字符。如果在字符串文字上完成,这将是未定义的行为。

我个人推荐第一种方法。

【讨论】:

  • 谢谢。我希望避免复制,但看起来我必须这样做。
  • 如果你在第二个例子中颠倒循环的顺序,(i = col_count -1; i &gt;=0; --i)你不需要保存/恢复原始字符。
  • @DavidGelhar,这是真的。如果 OP 不关心缓冲区被破坏,他当然可以这样做。不过,我通常倾向于不向后使用fors,因为如果将i 更改为unsigned,则会出现无限循环。
【解决方案3】:

您可以使用scanf()"%2c" 转换说明符将两个字符读入一个正确以零结尾的数组,然后将其转换为十进制(使用strtol() 或类似的东西)......但那是对接-丑陋。再说一次,不比格式本身丑。

一般来说,*scanf() 是解析输入的一个非常糟糕的选择,因为如果输入格式错误,它的行为会有些棘手。从长远来看,您最好将文件读入内部缓冲区并进行自己的自定义解析/错误处理。

【讨论】:

  • 您的解决方案中的'\n' 会发生什么情况?此外,OP 没有使用scanf,但确实在缓冲区上使用sscanf
  • 我没有提到\n,因为我只是给出了一个提示,而不是一个解决方案——因为 OP 可能已经复制并粘贴了它,而忽略了我的 other 提示scanf()(以及fscanf()sscanf() 以及所有其他*scanf()'s)对于解析输入并不是那么好。添加了 * 以明确我的意思是整个函数系列。
  • 其实sscanf是个很棒的功能。 scanf 据说不好,因为在解析错误的情况下您无法恢复输入,但 sscanf 被广泛使用。
  • @Shahbaz: *scanf() 不会告诉你它到底在哪里失败,或者是什么导致了失败。它只是告诉你有多少转换是成功的。 sscanf() 也是如此,并且会产生非常愚蠢的错误消息,例如“解析失败,检查输入”。
【解决方案4】:

不使用scanf。应该很快……

void parse_columns(const char *line)
    {
    char buf[3];
    buf[2] = '\0';

    int i = 0;
    for (const char *c = &line[0]; *c; c++, i++)
        {
        buf[i] = *c;
        if (1 == i)
            {
            printf("%d\n", atoi(buf));
            i = -1;
            }
        }
    }

【讨论】:

  • 如果您正在寻找“快速”,您应该找到绕过printf( atoi() )...的方法...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多