一种 cstdio 方法
嗯,有很多方法可以将行拆分为 name 和 hours 工作。简单的方法是使用C fscanf()(对于直接读取来说很脆弱)或将整行读入std:string,然后使用.c_str()和sscanf()将行分隔成name和hours(输入行格式的变化不会导致读取所有后续行失败)。使用fscanf() 或sscanf(),您可以使用格式字符串" %[^0-9]%lf" 将行解析为name 和hours 的double 值(您需要从name 修剪尾随空格)。你可以这样做:
#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <cstdio>
#define NAMSZ 128
int main (int argc, char **argv) {
if (argc < 2) { /* validate 1 argument given for filename */
std::cerr << "error: filename required as 1st argument.\n";
return 1;
}
std::ifstream f (argv[1]); /* open filename provided as 1st argument */
if (!f.is_open()) { /* validate file is open for reading */
std::cerr << "file open failed: " << argv[1] << '\n';
return 1;
}
std::string tmp {};
while (getline (f, tmp)) { /* read each line into tmp */
char name[NAMSZ] = ""; /* buffer to hold name */
double hours = 0.; /* double to hold hours */
/* separate name & hours from line (protect array bound w/field-width) */
if (sscanf(tmp.c_str(), " %127[^0-9]%lf", name, &hours) == 2)
std::cout << std::left << std::setw(32) << name << hours << '\n';
}
}
使用/输出示例
在文件dat/hoursworked.txt 中输入您的示例,您将收到以下信息:
$ ./bin/workerhours_cstdio dat/hoursworked.txt
John Smith 8
John Smith 7
John Smith 8
Jane Jones 9
Jane Jones 8
Maxwell Ko 7
Maxwell Ko 8
Maxwell Ko 8
Maxwell Ko 8
但现在剩下的就是如何收集所有相似的名字,然后总结工作时间。
(注意: 混合使用 csdtio 函数和 iostream 是完全可以的,并且在许多情况下将提供比其他方法更好的性能。请参阅 C++ iostreams: Unexpected but legal multithreaded behaviour 中有关混合使用的其他说明)
映射每个姓名的总工作时间
当您考虑协调一组独特的项目时,您首先想到的应该是std::map 或std::unordered_map。两者都提供了一种基于唯一key 关联唯一对象集合的方法。 (在这种情况下你的name)。 unordered_map 不以任何排序顺序保存对象,而 map 根据您提供的排序功能(或默认值,例如 std::greater)对对象进行排序。
在简单的情况下,您可以在<std::string, double> 之间创建一个映射,然后使用.find() 成员函数来确定映射中是否已经存在name,如果存在,只需添加当前的小时数线到现有的映射,如果没有,添加新的name,hours 对作为新元素。
要完成您的操作,您只需从名称中删除任何剩余的空格(上面未完成)并使用map(或unordered_map)创建唯一的name/hours 值对。只需简单的添加即可:
#include <map>
...
std::map<std::string, double> workers {}; /* map of workers */
...
/* separate name & hours from line (protect array bound w/field-width) */
if (sscanf(tmp.c_str(), " %127[^0-9]%lf", name, &hours) == 2) {
tmp = name; /* re-use tmp to make std::string */
while (isspace(tmp.back())) /* while trailing whitespace remains */
tmp.pop_back(); /* trim from end of sting */
auto srch = workers.find(tmp); /* search for name in map */
if (srch != workers.end()) /* if name already in map */
srch->second += hours; /* add to hours */
else /* name not found */
workers[tmp] = hours; /* add new name to map */
}
所以上面,在分离了初始的name 和hours 字符串之后,std::string tmp 被重新用于从name 创建一个std::string,然后isspace(tmp.back()) 用于检查是否最后一个字符是空格,如果是,则使用tmp.pop_back() 将其删除。
使用std::map workers,执行搜索以查看tmp 中的名称是否已存在,如果存在,则将小时数简单地添加到现有小时数(您使用workers->first 来引用@ 987654368@(名称)和workers->second 在使用srch 迭代器时引用映射值(hours)。如果名称不存在,则添加新映射。
总而言之,你会:
#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <cstdio>
#include <cctype>
#include <map>
#define NAMSZ 128
int main (int argc, char **argv) {
if (argc < 2) { /* validate 1 argument given for filename */
std::cerr << "error: filename required as 1st argument.\n";
return 1;
}
std::ifstream f (argv[1]); /* open filename provided as 1st argument */
if (!f.is_open()) { /* validate file is open for reading */
std::cerr << "file open failed: " << argv[1] << '\n';
return 1;
}
std::string tmp {};
std::map<std::string, double> workers {}; /* map of workers */
while (getline (f, tmp)) { /* read each line into tmp */
char name[NAMSZ] = ""; /* buffer to hold name */
double hours = 0.; /* double to hold hours */
/* separate name & hours from line (protect array bound w/field-width) */
if (sscanf(tmp.c_str(), " %127[^0-9]%lf", name, &hours) == 2) {
tmp = name; /* re-use tmp to make std::string */
while (isspace(tmp.back())) /* while trailing whitespace remains */
tmp.pop_back(); /* trim from end of sting */
auto srch = workers.find(tmp); /* search for name in map */
if (srch != workers.end()) /* if name already in map */
srch->second += hours; /* add to hours */
else /* name not found */
workers[tmp] = hours; /* add new name to map */
}
}
for (const auto& w : workers) /* output results */
std::cout << std::left << std::setw(32) << w.first << w.second << '\n';
}
使用/输出示例
$ ./bin/workerhours_cstdio_map dat/hoursworked.txt
Jane Jones 17
John Smith 23
Maxwell Ko 31
如果使用unordered_map,预计会出现类似以下的输出:
$ ./bin/workerhours_cstdio_umap dat/hoursworked.txt
Maxwell Ko 31
John Smith 23
Jane Jones 17
这可能是分离name 和hours 并协调小时总和的更简单方法之一。
使用 std::string 成员函数分隔名称和小时
当然也可以使用std::string 成员函数,如.find_first_of() 和.substr() 来处理分离。 (您的编译器至少需要支持-std=c++11)这种方法看起来类似于:
std::string name;
double hours;
...
const char *digits = "0123456789";
std::string line {};
while (getline (f, line)) { /* read line */
size_t hoursbegin = line.find_first_of(digits); /* find 1st [0-9] */
if (hoursbegin != std::string::npos) { /* valdiate found */
std::string tmp = line.substr(0, hoursbegin); /* get name */
while (isspace(tmp.back())) /* remove trailing */
tmp.pop_back(); /* .. spaces */
name = tmp; /* assign to name */
hours = stod(line.substr(hoursbegin)); /* assign to hours */
}
}
两者都一样好,都可以处理如下行:
John J. Doe, III, M.D. 10.8
而不是简单的名字、姓氏、小时线。
查看一下,如果您还有其他问题,请告诉我。