【问题标题】:Grab all integers from irregular strings in C从C中的不规则字符串中获取所有整数
【发布时间】:2011-06-07 21:00:55
【问题描述】:

我正在寻找一种(相对)简单的方法来解析随机字符串并从中提取所有整数并将它们放入一个数组中 - 这与其他一些类似的问题不同,因为我的字符串没有标准格式。

例子:

pt112parah salin10n m5:isstupid::42$%&%^*%7first3

我最终需要得到一个包含这些内容的数组:

112 10 5 42 7 3

我想要一种更有效的方法,然后逐个字符地通过字符串。

感谢您的帮助

【问题讨论】:

  • 我很确定这样做的唯一方法是逐个字符地进行,除非您知道要搜索的特定数字
  • 没有比逐个字符更有效的方法了。但是,您可能会发现一个隐藏循环的库函数。
  • 我只知道它将是一个小于 256 的非负数。我可以找到一个数字字符的索引,然后在该位置调用 sscanf 并重复,但我认为有一种更有效(或至少更清洁)的方法。
  • 逐字逐句有什么问题?你能想象另一种方式吗?
  • 我会想象一个像 strtok 一样解析字符串的函数,除了任何数字,而不是设置的标记。猜不出来~_~

标签: c parsing string ansi


【解决方案1】:

快速解决方案。我假设没有超出long 范围的数字,并且没有负号需要担心。如果这些是问题,那么您需要做更多的工作来分析strtol() 的结果,并且您需要检测'-' 后跟一个数字。

代码确实会遍历所有字符;我认为你无法避免这种情况。但它确实使用strtol() 来处理每个数字序列(一旦找到第一个数字),并在strtol() 停止的地方恢复(strtol() 很友好地告诉我们它停止转换的确切位置)。

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

int main(void)
{
    const char data[] = "pt112parah salin10n m5:isstupid::42$%&%^*%7first3";
    long results[100];
    int  nresult = 0;

    const char *s = data;
    char c;

    while ((c = *s++) != '\0')
    {
        if (isdigit(c))
        {
            char *end;
            results[nresult++] = strtol(s-1, &end, 10);
            s = end;
        }
    }

    for (int i = 0; i < nresult; i++)
        printf("%d: %ld\n", i, results[i]);
    return 0;
}

输出:

0: 112
1: 10
2: 5
3: 42
4: 7
5: 3

【讨论】:

  • 我注意到变量end是多余的;循环体可以是单个语句results[nresult++] = strtol(s-1, &amp;s, 10);。我还注意到我没有对整数数组进行溢出检查 - 这也应该在那里。
  • 更正:使用end 更好,因为它是常量正确的。使用 &amp;s 而不是 &amp;end 会导致在调用 strtol() 时出现编译警告。
【解决方案2】:

比一个字一个字地看更效率

不可能,因为您必须查看每个字符才能知道它不是整数。

现在,鉴于您必须逐个字符地遍历字符串,我建议您简单地将每个字符转换为 int 并检查:

//string tmp = ""; declared outside of loop.
//pseudocode for inner loop:
int intVal = (int)c;
if(intVal >=48 && intVal <= 57){ //0-9 are 48-57 when char casted to int.
    tmp += c;
}
else if(tmp.length > 0){
    array[?] = (int)tmp; // ? is where to add the int to the array.
    tmp = "";
}

数组将包含您的解决方案。

【讨论】:

  • 投到int没有意义,可以直接使用c;此外,48 和 57 仅对于基于 ASCII 的代码页是模糊且正确的,只需使用 '0''9',或 isdigit 函数。
【解决方案3】:

只是因为我整天都在写 Python,我想休息一下。声明一个数组会很棘手。要么你必须运行它两次才能计算出你有多少个数字(然后分配数组),或者像这个例子一样一个一个地使用数字。

注意 '0' 到 '9' 的 ASCII 字符是 48 到 57(即连续)。

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

int main(int argc, char **argv)
{
    char *input = "pt112par0ah salin10n m5:isstupid::42$%&%^*%7first3";

    int length = strlen(input);
    int value = 0;
    int i;
    bool gotnumber = false;
    for (i = 0; i < length; i++)
    {
        if (input[i] >= '0' && input[i] <= '9')
        {
            gotnumber = true;
            value = value * 10; // shift up a column
            value += input[i] - '0'; // casting the char to an int
        }
        else if (gotnumber) // we hit this the first time we encounter a non-number after we've had numbers
        {
            printf("Value: %d \n", value);
            value = 0;
            gotnumber = false;
        }
    }

    return 0;
}

编辑:以前的版本没有处理 0

【讨论】:

  • 有趣的解决方案。将使其适应我的代码,感谢您的帮助!
  • gotnumber 有什么用?没用过。
  • 没问题。如果它回答了您的问题,请打勾!
  • 查看else if的评论。它用于指示是否使用了value(即我们遇到了一个数字)。在使用该值后设置false。查看我的编辑以了解使用它的原因。
【解决方案4】:

另一种解决方案是使用strtok 函数

/* strtok example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] = "pt112parah salin10n m5:isstupid::42$%&%^*%7first3";
  char * pch;
  printf ("Splitting string \"%s\" into tokens:\n",str);
  pch = strtok (str," abcdefghijklmnopqrstuvwxyz:$%&^*");
  while (pch != NULL)
  {
    printf ("%s\n",pch);
    pch = strtok (NULL, " abcdefghijklmnopqrstuvwxyz:$%&^*");
  }
  return 0;
}

给予:

112
10
5
42
7
3

也许不是此任务的最佳解决方案,因为您需要指定将被视为标记的所有字符。但它是其他解决方案的替代方案。

【讨论】:

  • strtok() 立即排除扫描文字字符串或其他常量字符串,因为它修改了它正在解析的数组。
  • 是的,但这不在问题描述中,因为 OP 要求的不是每个字符的循环,所以我添加了这个,因为它是一个有趣的函数 :-)
  • 独立于我对使用 strtok() 修改字符串的保留(并且解析代码应该 IMNSHO 在没有明确许可的情况下永远不会修改输入字符串),显示的代码对出现的新字符没有弹性输入。例如,它会遇到第一个大写字母或重音字母的问题。使其具有弹性需要 strtok() 的 245 个字符的第二个参数(256 - 10 位 - NUL)。
【解决方案5】:

如果您不介意使用 C++ 而不是 C(通常没有充分的理由不这样做),那么您可以将解决方案缩减为仅两行代码(使用 AX 解析器生成器):

vector<int> numbers;
auto number_rule = *(*(axe::r_any() - axe::r_num()) 
   & *axe::r_num() >> axe::e_push_back(numbers));

现在测试一下:

std::string str = "pt112parah salin10n m5:isstupid::42$%&%^*%7first3";
number_rule(str.begin(), str.end());
std::for_each(numbers.begin(), numbers.end(), [](int i) { std::cout << "\ni=" << i; });

果然,你找回了你的号码。

另外,您在解析 unicode 宽字符串时无需更改任何内容:

std::wstring str = L"pt112parah salin10n m5:isstupid::42$%&%^*%7first3";
number_rule(str.begin(), str.end());
std::for_each(numbers.begin(), numbers.end(), [](int i) { std::cout << "\ni=" << i; });

果然,你得到了相同的数字。

【讨论】:

  • P.S.很容易将该规则修改为提取负数、浮点数、十六进制、八进制......
【解决方案6】:
#include <stdio.h>
#include <string.h>
#include <math.h>

int main(void)
{
    char *input = "pt112par0ah salin10n m5:isstupid::42$%&%^*%7first3";
    char *pos = input;
    int integers[strlen(input) / 2];   // The maximum possible number of integers is half the length of the string, due to the smallest number of digits possible per integer being 1 and the smallest number of characters between two different integers also being 1
    unsigned int numInts= 0;

    while ((pos = strpbrk(pos, "0123456789")) != NULL) // strpbrk() prototype in string.h
    {
        sscanf(pos, "%u", &(integers[numInts]));

        if (integers[numInts] == 0)
            pos++;
        else
            pos += (int) log10(integers[numInts]) + 1;        // requires math.h

        numInts++;
    }

    for (int i = 0; i < numInts; i++)
        printf("%d ", integers[i]);

    return 0;
}

查找整数是通过在偏移指针上重复调用strpbrk() 来完成的,指针再次偏移等于整数中位数的量,通过找到整数的以10 为底的对数来计算,并且加 1(整数为 0 时的特殊情况)。计算对数时无需在整数上使用abs(),正如您所说的整数将是非负数。如果你想更节省空间,你可以使用unsigned char integers[] 而不是int integers[],因为你说整数都是

【讨论】:

  • 让我的回答更恰当……不过,可能有一些方法可以简化它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-06
  • 1970-01-01
  • 2016-06-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多