【问题标题】:Extract numerical values from a string and average them从字符串中提取数值并将它们平均
【发布时间】:2020-06-15 23:09:32
【问题描述】:

我有一个包含这种格式数据的 .txt 文件:

xxxx: 0.9467,  
yyyy: 0.9489,  
zzzz: 0.78973,  
hhhh: 0.8874,  
yyyy: 0.64351,  
xxxx: 0.8743,

等等……

假设我的 C 程序接收字符串 yyyy 作为输入。程序应该简单地返回 .txt 文件中 yyyy 的所有实例及其所有数值的平均值。

int main() {
    FILE *filePTR;
    char fileRow[100000];

    if (fopen_s(&filePTR, "file.txt", "r") == 0) {
        while (fgets(fileRow, sizeof fileRow, filePTR) != NULL) {
            if (strstr(fileRow, "yyyy") != NULL) { // Input parameter
                printf("%s", fileRow);
            }
        }
        fclose(filePTR);
        printf("\nEnd of the file.\n");
    } else {
        printf("ERROR! Impossible to read the file.");
    }
    return 0;
}

这是我现在的代码。我不知道该怎么做:

  1. 隔离数值
  2. 实际上将它们转换为双精度类型
  3. 取平均值

我阅读了一些关于 strtok 函数的内容(刚刚开始),但我需要一些帮助...

【问题讨论】:

  • 考虑double d; if (sscanf(fileRow, " yyyy: %lf", &d) == 1) sum += d;。 FydRose,还需要更多吗?
  • 天哪,我的数学一团糟,谢谢@chqrlie...直接通过KB...

标签: c arrays string split strtok


【解决方案1】:

您已经走上了正确的道路,应该赞扬您在每次迭代时使用fgets() 从文件中读取完整的行,但是您选择strstr 并不能确保找到您要查找的前缀在行首。

此外,您希望避免对搜索字符串以及要打开的文件进行硬编码。 main() 通过argcargv 接受参数,让您在启动时将信息传递到程序中。见:C11 Standard - §5.1.2.2.1 Program startup(p1)。使用参数可以让您将要打开的文件名和要搜索的前缀作为参数传递给程序,从而消除对值进行硬编码的需要。 (这也消除了重新编译代码以读取另一个文件名或搜索另一个字符串的需要)

例如,您可以使用main() 的参数而不是硬编码值来打开任何文件并搜索任何前缀,只需使用类似以下内容:

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

#define MAXC 1024   /* if you need a constant, #define one (or more) */

int main (int argc, char **argv) {

    char buf[MAXC] = "", *str = NULL;   /* buffer for line and ptr to search str */
    size_t n = 0, len = 0;              /* counter and search string length */
    double sum = 0;                     /* sum of matching lines */
    FILE *fp = NULL;                    /* file pointer */

    if (argc < 3) { /* validate 2 arguments given - filename, search_string */ 
        fprintf (stderr, "error: insufficient number of arguments\n"
                "usage: %s filename search_string\n", argv[0]);
        return 1;
    }

    if (!(fp = fopen (argv[1], "r"))) { /* open/validate file open for reading */
        perror ("fopen-filename");
        return 1;
    }
    str = argv[2];                      /* set pointer to search string */
    len = strlen (str);                 /* get length of search string */
    ...

此时,在您的程序中,您已经打开了作为第一个参数传递的文件,并通过文件流指针fp 验证了它已打开以供读取。您已将要搜索的前缀作为第二个参数传入,将其分配给指针str,并获得了前缀的长度并存储在len中。

接下来,您希望将文件中的每一行读入buf,但不要尝试将前缀与strstr() 匹配,您可以使用strncmp()len 来比较从你的文件。如果找到前缀,则可以使用sscanf 从文件中解析double 值并将其添加到sum 并增加存储在n 中的值的数量,例如

    while (fgets (buf, MAXC, fp)) {             /* read each line into buf */
        if (strncmp (buf, str, len) == 0) {     /* if prefix matches */
            double tmp;                         /* temporary double for parse */
            /* parse with scanf, discarding prefix with assignment suppression */
            if (sscanf (buf, "%*1023[^:]: %lf", &tmp) == 1) {
                sum += tmp;             /* add value to sum */
                n++;                    /* increment count of values */
            }
        }
    }

(注意:sscanf()'*'赋值抑制运算符上方允许您读取和丢弃前缀和':',而无需存储第二个字符串中的前缀)

剩下的就是通过检查您的计数n 来检查值是否包含在sum 中,如果是,则输出前缀的平均值。或者,如果 n == 0 在文件中找不到前缀,例如:

    if (n)  /* if values found, output average */
        printf ("prefix '%s' avg: %.4f\n", str, sum / n);
    else    /* output not found */
        printf ("prefix '%s' -- not found in file.\n", str);
}

这基本上就是您所需要的。有了它,您可以从您喜欢的任何文件中读取并搜索任何前缀,只需将文件名和前缀作为前两个参数传递给您的程序。完整的例子是:

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

#define MAXC 1024   /* if you need a constant, #define one (or more) */

int main (int argc, char **argv) {

    char buf[MAXC] = "", *str = NULL;   /* buffer for line and ptr to search str */
    size_t n = 0, len = 0;              /* counter and search string length */
    double sum = 0;                     /* sum of matching lines */
    FILE *fp = NULL;                    /* file pointer */

    if (argc < 3) { /* validate 2 arguments given - filename, search_string */ 
        fprintf (stderr, "error: insufficient number of arguments\n"
                "usage: %s filename search_string\n", argv[0]);
        return 1;
    }

    if (!(fp = fopen (argv[1], "r"))) { /* open/validate file open for reading */
        perror ("fopen-filename");
        return 1;
    }
    str = argv[2];                      /* set pointer to search string */
    len = strlen (str);                 /* get length of search string */

    while (fgets (buf, MAXC, fp)) {             /* read each line into buf */
        if (strncmp (buf, str, len) == 0) {     /* if prefix matches */
            double tmp;                         /* temporary double for parse */
            /* parse with scanf, discarding prefix with assignment suppression */
            if (sscanf (buf, "%*1023[^:]: %lf", &tmp) == 1) {
                sum += tmp;             /* add value to sum */
                n++;                    /* increment count of values */
            }
        }
    }

    if (n)  /* if values found, output average */
        printf ("prefix '%s' avg: %.4f\n", str, sum / n);
    else    /* output not found */
        printf ("prefix '%s' -- not found in file.\n", str);
}

使用/输出示例

使用您存储在dat/prefixdouble.txt中的数据文件,您可以搜索文件中的每个前缀并获得平均值,例如

$ ./bin/prefixaverage dat/prefixdouble.txt hhhh
prefix 'hhhh' avg: 0.8874

$ ./bin/prefixaverage dat/prefixdouble.txt xxxx
prefix 'xxxx' avg: 0.9105

$ ./bin/prefixaverage dat/prefixdouble.txt yyyy
prefix 'yyyy' avg: 0.7962

$ ./bin/prefixaverage dat/prefixdouble.txt zzzz
prefix 'zzzz' avg: 0.7897

$ ./bin/prefixaverage dat/prefixdouble.txt foo
prefix 'foo' -- not found in file.

比每次要搜索另一个前缀时都重新编译要容易得多。如果您还有其他问题,请仔细查看并告诉我。

【讨论】:

  • 非常感谢您的详细解释。不幸的是,尽管您的回答质量很高,但我不是 C 程序员...因此我很难理解如何将这两个参数传递给 main()...
  • 这两个参数int argc (argument count) 只是告诉你有多少命令行参数。 (第一个参数始终是正在运行的程序名称,因此第一个用户参数是argv[1]),char *argv[](参数向量)是一个指向以 nul 结尾的字符串的指针数组,第一个指针在最后一个参数之后设置为NULL 作为sentinel NULL。在您的情况下,有 2 个用户参数(总共 3 个带有程序名称)在第一个示例中,argv[1]"dat/prefixdouble.txt"(文件名),argv[2]"hhhh"(平均值的前缀)跨度>
  • 应该可以正常编译。您将收到4996 警告,除非您包含#define _CRT_SECURE_NO_WARNINGS,但这只是因为Microsoft 为scanf_s, etc.. 实施了Annex K 扩展,否则这是plan-Jane vanilla C。您会遇到什么错误? (并且您是编译为 C 还是 C++,您需要 /Tc 选项(或 /TC 对于所有源)编译为 C)
  • 没错,在文件顶部包含#define _CRT_SECURE_NO_WARNINGS,或者使用/wd4996 禁用警告(您已将其解释为错误)。 (这是 Microsoft VS 的东西,而不是 C 的东西......)或者您可以使用 ..._s 函数代替,但是您的代码对于大多数编译器来说是不可移植的。见Compiler Warning (level 3) C4996 | Microsoft Docs
  • 好吧,打开你的选项并设置你的命令行选项,或者真的,只是打开一个命令提示符(或PowerShell)或 VS 开发人员的命令提示符 并切换到您的项目目录(通常在DEBUG 下),您可以在命令提示符下使用yourprogname.exe filetoread prefix 运行程序因为您最终要处理这些“我在哪里可以找到正确的设置对话框?”问题而不是实际的编程问题。 命令行程序使用命令行更容易:)
猜你喜欢
  • 2018-07-30
  • 2018-07-06
  • 2018-01-01
  • 2020-04-21
  • 1970-01-01
  • 2017-10-01
  • 2017-03-08
  • 2018-04-25
  • 1970-01-01
相关资源
最近更新 更多