是否可以从此代码打印特定行?
Is it possible to print specific lines out of this code?
我正在尝试从我的程序中打印出任何必要的东西。它所做的是从文本文件中获取一长串列表,并根据第一选择和 GPA 对其进行排序,然后将其放入向量中。
我设法按 First
选择和 GPA
进行排序,但是如何删除不需要的输出?
我知道我之前问过这个问题,但我认为之前没有问对,我已经编辑了一些。
这是我的Txt文件的例子(每一行的顺序是第一选择,第二选择,第三选择,GPA,姓名):
CC,DR,TP,3.8,AlexKong
SN,SM,TP,4,MarcusTan
DR,TP,SC,3.6,AstaGoodwin
SC,TP,DR,2.8,MalcumYeo
SN,SM,TP,3.7,DavidLim
SN,SM,TP,3.2,SebastianHo
SC,TP,DR,4,PranjitSingh
DR,TP,SC,3.7,JacobMa
and so on...
这是我现在的输出(它是一个长向量):
TP,DR,SC,4,SitiZakariah
TP,DR,SC,3.9,MuttuSami
TP,DR,SC,3.5,SabrinaEster
TP,DR,SC,3,KarimIlham
TP,DR,SC,3,AndryHritik
SN,SM,TP,4,JasonTan
SN,SM,TP,3.8,MarcusOng
SN,SM,TP,3.7,DavidLim
SN,SM,TP,3.4,MollyLau
SN,SM,TP,3.2,SebastianHo
SN,SM,TP,3.2,NurAfiqah
SN,SM,TP,2.4,TanXiWei
SC,TP,DR,4,SallyYeo
SC,TP,DR,4,PranjitSingh
SC,TP,DR,3.6,RanjitSing
SC,TP,DR,2.8,MalcumYeo
SC,TP,DR,2.8,AbdulHalim
SC,TP,DR,2.7,AlifAziz
DR,TP,SC,3.9,SitiAliyah
DR,TP,SC,3.9,LindaChan
DR,TP,SC,3.8,SohLeeHoon
DR,TP,SC,3.7,PrithikaSari
DR,TP,SC,3.7,NurAzizah
DR,TP,SC,3.7,JacobMa
DR,TP,SC,3.6,AstaGoodwin
CC,DR,TP,3.9,MuruArun
CC,DR,TP,3.7,DamianKoh
CC,DR,TP,3.3,MattWiliiams
CC,DR,TP,3.3,IrfanMuhaimin
这是我需要的输出(基本上学生以 CC
作为他们的第一选择而不显示 3 个选项。我不想要没有 CC
作为他们的第一选择的其他选项.我已经设法在没有以下 3 个选择的情况下打印输出。):
3.9,MuruArun
3.8,AlexKong
3.7,DamianKoh
3.3,MattWiliiams
3.3,IrfanMuhaimin
这是我的程序:
#include <iostream>
#include <vector>
#include <fstream>
#include <string>
#include <algorithm>
using namespace std;
struct greater
{
template<class T>
bool operator()(T const &a, T const &b) const { return a > b; }
};
void main()
{
vector<string> v;
ifstream File;
File.open("DSA.txt");
if (!File.is_open()) return;
string line;
string Name;
string GPA;
string First;
string Second;
string Third;
getline(File, First, ',');
getline(File, Second, ',');
getline(File, Third, ',');
getline(File, Name, ',');
getline(File, GPA, '\n');
cout << "Round 1:\n";
if (First == "CC")
while (File>>line)
{
v.push_back(line);
}
sort(v.begin(), v.end(), greater());
for (int i = 0; i < v.size(); i++)
{
cout << v[i].substr(9) << endl; //remove first 3 choices from output
}
}
这是我过滤输出的尝试:
if (First == "CC")
while (File>>line)
{
v.push_back(line);
}
sort(v.begin(), v.end(), greater());
for (int i = 0; i < v.size(); i++)
{
cout << v[i].substr(9) << endl;
}
我认为如果我获取行并创建一个 if 条件来分隔 CC(如果首选是 CC,则条件为真)那么我只打印首选 CC 的那些,而忽略其余部分。所以基本上我会尝试搜索 CC 作为首选。
但显然我错了。所以我希望如果有人知道如何过滤输出
前一点:
正如评论部分所指出的那样,using namespace std;
是一个错误的选择,您的代码有一个示例说明了其中一个原因,即 greater
的重新定义已经存在于命名空间。
provided link 有进一步的解释和备选方案。
至于你的代码,如果目标是输出行,从 CC
开始,没有选项,按 GPA
排序,据我所知,有更简单的方法可以做到这一点,例如,您可以使用 std::find
仅解析开头带有 "CC"
的行并从那里开始工作。
您也可以使用 std::string::starts_with
,但它仅适用于 C++20,因此我将使用第一个选项。
int main()
{
std::vector<std::string> v;
std::ifstream File;
File.open("DSA.txt");
if (!File.is_open())
return EXIT_FAILURE;
std::string line;
while (File >> line)
{
if (line.find("CC") == 0) // find lines that start with CC
v.push_back(&line[9]); // add lines without the options
} // or v.push_back(line.substr(9));
sort(v.begin(), v.end(), std::greater<std::string>()); //sort the lines
std::cout << "GPA" << "\t" << "Name" <<"\n\n"; // Title for the table
for (auto& str : v) //print the lines
{
std::replace(str.begin(), str.end(), ',', '\t'); //let's replace de comma
std::cout << str << "\n";
}
return EXIT_SUCCESS;
}
以你的样本为例,这将输出:
GPA Name
3.9 MuruArun
3.7 DamianKoh
3.3 MattWiliiams
3.3 IrfanMuhaimin
第二个或第三个选项中带有“CC”的行将不会被解析,这是我们的目标。
注:
这种按字符串排序的方法是可行的,并且在这种情况下有效,因为 GPA
值小于 10,否则我们将不得不转换 GPA
字段并按其值对行进行排序, e.g.: 10 大于 9 但作为一个字符串,它会首先排序,因为按字典顺序,9 会被认为更大,字符 9大于字符 1.
如你所见,我使用了默认的greater
模板,在这种情况下你不需要自己制作,你可以直接使用这个。
还有一点,main
必须有 int
return 类型。
请注意,对数据记录进行排序和过滤是 DBMS 的经典任务。
因此,与其编写程序,不如考虑将 CSV 加载到您选择的 DBMS 中(MonetDB 是一个很好的用于分析的 FOSS DBMS),比如加载到名为 [=11= 的 table 中] 然后发出适当的查询,例如
SELECT * FROM people WHERE first_choice = 'CC' ORDER BY gpa;
(即 SQL 查询)以获得您想要的输出。
一些 DBMS 甚至可以在本地使用 CSV 文件,在这种情况下,您无需加载任何内容,只需将 DBMS 指向 CSV 文件即可。
最后,很抱歉提出一些粗略的建议,但是 - 如果您愿意对此更加“手动” - 像 LibreOffice Calc 或 MS Excel 这样的电子表格应用程序可以导入 CSV;您可以使用 AutoFilter 功能仅显示第一个选项为 CC
的人,并使用 GPA 列上的自动筛选器下拉菜单对 GPA 进行降序排序。
PS - 这当然不是要贬低其他有效答案。
显然你使用了错误的方法。这必须改变。
首先,我们需要分析什么问题。所以,我们有一个 文件 有 多行 。许多行中的每一行都包含一个学生的信息/值。这些值由逗号 分隔 。
此类数据通常称为 CSV --> 逗号分隔值。
SO 上有大量 post 来解释如何读取 CSV 文件。
无论如何。完成初步分析后,我们现在必须开始思考,我们如何解决这个问题。查看一行中的数据,我们注意到它总是以相同的方式结构化。出于这个原因,我们定义了一个结构,它将包含一个学生的值。我们称这个新结构为“Student”。它将被定义为:
// Data for one student
struct Student {
std::string first{};
std::string second{};
std::string third{};
double GPA{};
std::string name{};
};
请注意,GPA 将存储为双精度值而不是字符串,因为我们可能想要进行一些数学计算。
下一个要求是我们有 许多 行学生数据。因此,我们将把许多学生存储在一个容器中。在这里我们 select std::vector
,因为它可以增长 dynamically.So 所有 studnets 的所有数据都可以存储在
// Here we will store all student swith all data
std::vector<Student> student{};
接下来,我们要从文件中读取所有数据。那么,让我们定义一个文件流变量并将文件名作为构造函数参数。这将尝试自动打开文件。而且,如果不再使用此变量并超出范围,则该文件将自动关闭。
我们检查文件是否打开。因为 bool 运算符和 !流运算符被覆盖为 return 文件的状态,我们可以简单地写:
if (fileStream) {
接下来我们要读取文件的 多 行,其中包含学生数据。我们将使用 std::getline
函数来读取完整的一行。我们将在一个 while 循环中使用这个函数。该函数将再次 return 对 ifstream 的引用。而且,如上所述,它有一个 bool 运算符。因此,读取将在 EOF (End-Of_file) 处停止。
while (std::getline(fileStream, line))
然后,我们的变量“line”中就有了完整的一行。这需要拆分成它的子组件。 "SN,SM,TP,3.7,DavidLim" 需要拆分为 "SN", "SM","TP", 3.7, "DavidLim".
拆分 CSV 字符串有很多可能的解决方案。我将使用 std::getline
的方法。在此 post 的底部,我展示了一些进一步的示例。
因此,为了使用 iostream 工具从字符串中提取数据,我们可以使用 std::istringstream
。我们将把这条线放入其中,然后可以像从任何其他流中一样提取数据:
std::istringstream iss{ line };
然后我们将再次使用 std::getline
函数从字符串流中提取我们需要的数据。并且,在提取所有内容之后,我们将向我们的目标向量添加一个完整的 Student 记录:
student.push_back(tempStudent);
现在,我们的向量中有所有学生数据,我们可以使用 C++ 的所有函数和算法对数据进行各种操作。
对于过滤,我们将遍历向量中的所有数据,然后使用 if
语句找出当前学生记录是否满足条件。然后我们打印出来。
参见下面的示例程序:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>
// Data for one student
struct Student {
std::string first{};
std::string second{};
std::string third{};
double GPA{};
std::string name{};
};
const std::string fileName{ "r:\DSA.txt" };
int main() {
// Here we will store all student swith all data
std::vector<Student> student{};
// Open the source file
std::ifstream fileStream{ fileName };
// Check, if file could be opened
if (fileStream) {
// One complete line of the source file
std::string line{};
// Now read all lines of the source file
while (std::getline(fileStream, line)) {
// Now we have a complete line like "SN,SM,TP,4,MarcusTan\n" in the variable line
// In order to extract from this line, we will put it in a std::istringstream
std::istringstream iss{ line };
// Now we can extract from this string stream all our needed strings
Student tempStudent{};
// Extract all data
std::getline(iss, tempStudent.first,',');
std::getline(iss, tempStudent.second,',');
std::getline(iss, tempStudent.third, ',');
std::string tempGPA{}; std::getline(iss, tempGPA, ','); tempStudent.GPA = std::stod(tempGPA);
std::getline(iss, tempStudent.name);
// Add this data for one student to the vector with all students
student.push_back(tempStudent);
}
// Now, all Students are available
// If you want to sort, then do it know. We can sort for any field.
// As an example, we sort by name. Anything else also possible
std::sort(student.begin(), student.end(), [](const Student& s1, const Student& s2) { return s1.name < s2.name; });
// Now, we make a filtered output
// Iterate over all students
for (const Student& s : student) {
// Check, if condition is fullfilled
if (s.first == "CC") {
std::cout << s.GPA << ", " << s.name << '\n';
}
}
}
else {
// There was a problem with opening the input source file. Show error message.
std::cerr << "\n\nError: Could not open file '" << fileName << "'\n\n";
}
}
但这很C-Style。在现代 C++ 中,我们会采用不同的方式。面向对象的方法将数据和方法(对该数据进行操作)保存在一个 class 或结构中。
所以,基本上我们会为结构定义一个提取器和插入器运算符,因为只有这个对象应该知道如何读取和写入它的数据。
那么事情就真的简单紧凑了。
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
// Data for one student
struct Student {
std::string first{};
std::string second{};
std::string third{};
double GPA{};
std::string name{};
friend std::istream& operator >> (std::istream& is, Student& s) {
char comma{};
return std::getline(std::getline(std::getline(std::getline(is, s.first,','), s.second,','), s.third,',') >> s.GPA >> comma, s.name);
}
friend std::ostream& operator << (std::ostream& os, const Student& s) {
return os << s.first << '\t' << s.second << '\t' << s.third << '\t' << s.GPA << '\t' << s.name;
}
};
const std::string fileName{ "r:\DSA.txt" };
int main() {
// Open the source file and check, if it could be opened
if (std::ifstream fileStream{ fileName }; fileStream) {
// Read the complet CSV file and parse it
std::vector student(std::istream_iterator<Student>(fileStream), {});
// Show all recors with first==CC
std::copy_if(student.begin(), student.end(), std::ostream_iterator<Student>(std::cout, "\n"), [](const Student& s) { return s.first == "CC"; });
}
return 0;
}
因此,您有一个 one-liner 用于读取所有学生数据。然后你可以应用标准库中的各种算法。
就是这样。
拆分字符串
将字符串拆分为标记是一项非常古老的任务。有许多可用的解决方案。都有不同的属性。有的难理解,有的难开发,有的比较复杂,慢的或者快的或者灵活的或者不灵活的。
备选方案
- 手工制作,许多变体,使用指针或迭代器,可能难以开发且容易出错。
- 使用旧式
std::strtok
函数。也许不安全。也许不应该再使用了
std::getline
。最常用的实现。但实际上是一种“误用”,并没有那么灵活
- 使用专门为此目的开发的专用现代功能,最灵活,最适合 STL 环境和算法环境。但是比较慢。
请在一段代码中查看 4 个示例。
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <regex>
#include <algorithm>
#include <iterator>
#include <cstring>
#include <forward_list>
#include <deque>
using Container = std::vector<std::string>;
std::regex delimiter{ "," };
int main() {
// Some function to print the contents of an STL container
auto print = [](const auto& container) -> void { std::copy(container.begin(), container.end(),
std::ostream_iterator<std::decay<decltype(*container.begin())>::type>(std::cout, " ")); std::cout << '\n'; };
// Example 1: Handcrafted -------------------------------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Search for comma, then take the part and add to the result
for (size_t i{ 0U }, startpos{ 0U }; i <= stringToSplit.size(); ++i) {
// So, if there is a comma or the end of the string
if ((stringToSplit[i] == ',') || (i == (stringToSplit.size()))) {
// Copy substring
c.push_back(stringToSplit.substr(startpos, i - startpos));
startpos = i + 1;
}
}
print(c);
}
// Example 2: Using very old strtok function ----------------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Split string into parts in a simple for loop
#pragma warning(suppress : 4996)
for (char* token = std::strtok(const_cast<char*>(stringToSplit.data()), ","); token != nullptr; token = std::strtok(nullptr, ",")) {
c.push_back(token);
}
print(c);
}
// Example 3: Very often used std::getline with additional istringstream ------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Put string in an std::istringstream
std::istringstream iss{ stringToSplit };
// Extract string parts in simple for loop
for (std::string part{}; std::getline(iss, part, ','); c.push_back(part))
;
print(c);
}
// Example 4: Most flexible iterator solution ------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});
//
// Everything done already with range constructor. No additional code needed.
//
print(c);
// Works also with other containers in the same way
std::forward_list<std::string> c2(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});
print(c2);
// And works with algorithms
std::deque<std::string> c3{};
std::copy(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {}, std::back_inserter(c3));
print(c3);
}
return 0;
}
请启用 C++17 进行编译。
可惜没人会看。
我正在尝试从我的程序中打印出任何必要的东西。它所做的是从文本文件中获取一长串列表,并根据第一选择和 GPA 对其进行排序,然后将其放入向量中。
我设法按 First
选择和 GPA
进行排序,但是如何删除不需要的输出?
我知道我之前问过这个问题,但我认为之前没有问对,我已经编辑了一些。
这是我的Txt文件的例子(每一行的顺序是第一选择,第二选择,第三选择,GPA,姓名):
CC,DR,TP,3.8,AlexKong
SN,SM,TP,4,MarcusTan
DR,TP,SC,3.6,AstaGoodwin
SC,TP,DR,2.8,MalcumYeo
SN,SM,TP,3.7,DavidLim
SN,SM,TP,3.2,SebastianHo
SC,TP,DR,4,PranjitSingh
DR,TP,SC,3.7,JacobMa
and so on...
这是我现在的输出(它是一个长向量):
TP,DR,SC,4,SitiZakariah
TP,DR,SC,3.9,MuttuSami
TP,DR,SC,3.5,SabrinaEster
TP,DR,SC,3,KarimIlham
TP,DR,SC,3,AndryHritik
SN,SM,TP,4,JasonTan
SN,SM,TP,3.8,MarcusOng
SN,SM,TP,3.7,DavidLim
SN,SM,TP,3.4,MollyLau
SN,SM,TP,3.2,SebastianHo
SN,SM,TP,3.2,NurAfiqah
SN,SM,TP,2.4,TanXiWei
SC,TP,DR,4,SallyYeo
SC,TP,DR,4,PranjitSingh
SC,TP,DR,3.6,RanjitSing
SC,TP,DR,2.8,MalcumYeo
SC,TP,DR,2.8,AbdulHalim
SC,TP,DR,2.7,AlifAziz
DR,TP,SC,3.9,SitiAliyah
DR,TP,SC,3.9,LindaChan
DR,TP,SC,3.8,SohLeeHoon
DR,TP,SC,3.7,PrithikaSari
DR,TP,SC,3.7,NurAzizah
DR,TP,SC,3.7,JacobMa
DR,TP,SC,3.6,AstaGoodwin
CC,DR,TP,3.9,MuruArun
CC,DR,TP,3.7,DamianKoh
CC,DR,TP,3.3,MattWiliiams
CC,DR,TP,3.3,IrfanMuhaimin
这是我需要的输出(基本上学生以 CC
作为他们的第一选择而不显示 3 个选项。我不想要没有 CC
作为他们的第一选择的其他选项.我已经设法在没有以下 3 个选择的情况下打印输出。):
3.9,MuruArun
3.8,AlexKong
3.7,DamianKoh
3.3,MattWiliiams
3.3,IrfanMuhaimin
这是我的程序:
#include <iostream>
#include <vector>
#include <fstream>
#include <string>
#include <algorithm>
using namespace std;
struct greater
{
template<class T>
bool operator()(T const &a, T const &b) const { return a > b; }
};
void main()
{
vector<string> v;
ifstream File;
File.open("DSA.txt");
if (!File.is_open()) return;
string line;
string Name;
string GPA;
string First;
string Second;
string Third;
getline(File, First, ',');
getline(File, Second, ',');
getline(File, Third, ',');
getline(File, Name, ',');
getline(File, GPA, '\n');
cout << "Round 1:\n";
if (First == "CC")
while (File>>line)
{
v.push_back(line);
}
sort(v.begin(), v.end(), greater());
for (int i = 0; i < v.size(); i++)
{
cout << v[i].substr(9) << endl; //remove first 3 choices from output
}
}
这是我过滤输出的尝试:
if (First == "CC")
while (File>>line)
{
v.push_back(line);
}
sort(v.begin(), v.end(), greater());
for (int i = 0; i < v.size(); i++)
{
cout << v[i].substr(9) << endl;
}
我认为如果我获取行并创建一个 if 条件来分隔 CC(如果首选是 CC,则条件为真)那么我只打印首选 CC 的那些,而忽略其余部分。所以基本上我会尝试搜索 CC 作为首选。 但显然我错了。所以我希望如果有人知道如何过滤输出
前一点:
正如评论部分所指出的那样,using namespace std;
是一个错误的选择,您的代码有一个示例说明了其中一个原因,即 greater
的重新定义已经存在于命名空间。
provided link 有进一步的解释和备选方案。
至于你的代码,如果目标是输出行,从 CC
开始,没有选项,按 GPA
排序,据我所知,有更简单的方法可以做到这一点,例如,您可以使用 std::find
仅解析开头带有 "CC"
的行并从那里开始工作。
您也可以使用 std::string::starts_with
,但它仅适用于 C++20,因此我将使用第一个选项。
int main()
{
std::vector<std::string> v;
std::ifstream File;
File.open("DSA.txt");
if (!File.is_open())
return EXIT_FAILURE;
std::string line;
while (File >> line)
{
if (line.find("CC") == 0) // find lines that start with CC
v.push_back(&line[9]); // add lines without the options
} // or v.push_back(line.substr(9));
sort(v.begin(), v.end(), std::greater<std::string>()); //sort the lines
std::cout << "GPA" << "\t" << "Name" <<"\n\n"; // Title for the table
for (auto& str : v) //print the lines
{
std::replace(str.begin(), str.end(), ',', '\t'); //let's replace de comma
std::cout << str << "\n";
}
return EXIT_SUCCESS;
}
以你的样本为例,这将输出:
GPA Name
3.9 MuruArun
3.7 DamianKoh
3.3 MattWiliiams
3.3 IrfanMuhaimin
第二个或第三个选项中带有“CC”的行将不会被解析,这是我们的目标。
注:
这种按字符串排序的方法是可行的,并且在这种情况下有效,因为 GPA
值小于 10,否则我们将不得不转换 GPA
字段并按其值对行进行排序, e.g.: 10 大于 9 但作为一个字符串,它会首先排序,因为按字典顺序,9 会被认为更大,字符 9大于字符 1.
如你所见,我使用了默认的greater
模板,在这种情况下你不需要自己制作,你可以直接使用这个。
还有一点,main
必须有 int
return 类型。
请注意,对数据记录进行排序和过滤是 DBMS 的经典任务。
因此,与其编写程序,不如考虑将 CSV 加载到您选择的 DBMS 中(MonetDB 是一个很好的用于分析的 FOSS DBMS),比如加载到名为 [=11= 的 table 中] 然后发出适当的查询,例如
SELECT * FROM people WHERE first_choice = 'CC' ORDER BY gpa;
(即 SQL 查询)以获得您想要的输出。
一些 DBMS 甚至可以在本地使用 CSV 文件,在这种情况下,您无需加载任何内容,只需将 DBMS 指向 CSV 文件即可。
最后,很抱歉提出一些粗略的建议,但是 - 如果您愿意对此更加“手动” - 像 LibreOffice Calc 或 MS Excel 这样的电子表格应用程序可以导入 CSV;您可以使用 AutoFilter 功能仅显示第一个选项为 CC
的人,并使用 GPA 列上的自动筛选器下拉菜单对 GPA 进行降序排序。
PS - 这当然不是要贬低其他有效答案。
显然你使用了错误的方法。这必须改变。
首先,我们需要分析什么问题。所以,我们有一个 文件 有 多行 。许多行中的每一行都包含一个学生的信息/值。这些值由逗号 分隔 。
此类数据通常称为 CSV --> 逗号分隔值。
SO 上有大量 post 来解释如何读取 CSV 文件。
无论如何。完成初步分析后,我们现在必须开始思考,我们如何解决这个问题。查看一行中的数据,我们注意到它总是以相同的方式结构化。出于这个原因,我们定义了一个结构,它将包含一个学生的值。我们称这个新结构为“Student”。它将被定义为:
// Data for one student
struct Student {
std::string first{};
std::string second{};
std::string third{};
double GPA{};
std::string name{};
};
请注意,GPA 将存储为双精度值而不是字符串,因为我们可能想要进行一些数学计算。
下一个要求是我们有 许多 行学生数据。因此,我们将把许多学生存储在一个容器中。在这里我们 select std::vector
,因为它可以增长 dynamically.So 所有 studnets 的所有数据都可以存储在
// Here we will store all student swith all data
std::vector<Student> student{};
接下来,我们要从文件中读取所有数据。那么,让我们定义一个文件流变量并将文件名作为构造函数参数。这将尝试自动打开文件。而且,如果不再使用此变量并超出范围,则该文件将自动关闭。
我们检查文件是否打开。因为 bool 运算符和 !流运算符被覆盖为 return 文件的状态,我们可以简单地写:
if (fileStream) {
接下来我们要读取文件的 多 行,其中包含学生数据。我们将使用 std::getline
函数来读取完整的一行。我们将在一个 while 循环中使用这个函数。该函数将再次 return 对 ifstream 的引用。而且,如上所述,它有一个 bool 运算符。因此,读取将在 EOF (End-Of_file) 处停止。
while (std::getline(fileStream, line))
然后,我们的变量“line”中就有了完整的一行。这需要拆分成它的子组件。 "SN,SM,TP,3.7,DavidLim" 需要拆分为 "SN", "SM","TP", 3.7, "DavidLim".
拆分 CSV 字符串有很多可能的解决方案。我将使用 std::getline
的方法。在此 post 的底部,我展示了一些进一步的示例。
因此,为了使用 iostream 工具从字符串中提取数据,我们可以使用 std::istringstream
。我们将把这条线放入其中,然后可以像从任何其他流中一样提取数据:
std::istringstream iss{ line };
然后我们将再次使用 std::getline
函数从字符串流中提取我们需要的数据。并且,在提取所有内容之后,我们将向我们的目标向量添加一个完整的 Student 记录:
student.push_back(tempStudent);
现在,我们的向量中有所有学生数据,我们可以使用 C++ 的所有函数和算法对数据进行各种操作。
对于过滤,我们将遍历向量中的所有数据,然后使用 if
语句找出当前学生记录是否满足条件。然后我们打印出来。
参见下面的示例程序:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>
// Data for one student
struct Student {
std::string first{};
std::string second{};
std::string third{};
double GPA{};
std::string name{};
};
const std::string fileName{ "r:\DSA.txt" };
int main() {
// Here we will store all student swith all data
std::vector<Student> student{};
// Open the source file
std::ifstream fileStream{ fileName };
// Check, if file could be opened
if (fileStream) {
// One complete line of the source file
std::string line{};
// Now read all lines of the source file
while (std::getline(fileStream, line)) {
// Now we have a complete line like "SN,SM,TP,4,MarcusTan\n" in the variable line
// In order to extract from this line, we will put it in a std::istringstream
std::istringstream iss{ line };
// Now we can extract from this string stream all our needed strings
Student tempStudent{};
// Extract all data
std::getline(iss, tempStudent.first,',');
std::getline(iss, tempStudent.second,',');
std::getline(iss, tempStudent.third, ',');
std::string tempGPA{}; std::getline(iss, tempGPA, ','); tempStudent.GPA = std::stod(tempGPA);
std::getline(iss, tempStudent.name);
// Add this data for one student to the vector with all students
student.push_back(tempStudent);
}
// Now, all Students are available
// If you want to sort, then do it know. We can sort for any field.
// As an example, we sort by name. Anything else also possible
std::sort(student.begin(), student.end(), [](const Student& s1, const Student& s2) { return s1.name < s2.name; });
// Now, we make a filtered output
// Iterate over all students
for (const Student& s : student) {
// Check, if condition is fullfilled
if (s.first == "CC") {
std::cout << s.GPA << ", " << s.name << '\n';
}
}
}
else {
// There was a problem with opening the input source file. Show error message.
std::cerr << "\n\nError: Could not open file '" << fileName << "'\n\n";
}
}
但这很C-Style。在现代 C++ 中,我们会采用不同的方式。面向对象的方法将数据和方法(对该数据进行操作)保存在一个 class 或结构中。
所以,基本上我们会为结构定义一个提取器和插入器运算符,因为只有这个对象应该知道如何读取和写入它的数据。
那么事情就真的简单紧凑了。
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
// Data for one student
struct Student {
std::string first{};
std::string second{};
std::string third{};
double GPA{};
std::string name{};
friend std::istream& operator >> (std::istream& is, Student& s) {
char comma{};
return std::getline(std::getline(std::getline(std::getline(is, s.first,','), s.second,','), s.third,',') >> s.GPA >> comma, s.name);
}
friend std::ostream& operator << (std::ostream& os, const Student& s) {
return os << s.first << '\t' << s.second << '\t' << s.third << '\t' << s.GPA << '\t' << s.name;
}
};
const std::string fileName{ "r:\DSA.txt" };
int main() {
// Open the source file and check, if it could be opened
if (std::ifstream fileStream{ fileName }; fileStream) {
// Read the complet CSV file and parse it
std::vector student(std::istream_iterator<Student>(fileStream), {});
// Show all recors with first==CC
std::copy_if(student.begin(), student.end(), std::ostream_iterator<Student>(std::cout, "\n"), [](const Student& s) { return s.first == "CC"; });
}
return 0;
}
因此,您有一个 one-liner 用于读取所有学生数据。然后你可以应用标准库中的各种算法。
就是这样。
拆分字符串
将字符串拆分为标记是一项非常古老的任务。有许多可用的解决方案。都有不同的属性。有的难理解,有的难开发,有的比较复杂,慢的或者快的或者灵活的或者不灵活的。
备选方案
- 手工制作,许多变体,使用指针或迭代器,可能难以开发且容易出错。
- 使用旧式
std::strtok
函数。也许不安全。也许不应该再使用了 std::getline
。最常用的实现。但实际上是一种“误用”,并没有那么灵活- 使用专门为此目的开发的专用现代功能,最灵活,最适合 STL 环境和算法环境。但是比较慢。
请在一段代码中查看 4 个示例。
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <regex>
#include <algorithm>
#include <iterator>
#include <cstring>
#include <forward_list>
#include <deque>
using Container = std::vector<std::string>;
std::regex delimiter{ "," };
int main() {
// Some function to print the contents of an STL container
auto print = [](const auto& container) -> void { std::copy(container.begin(), container.end(),
std::ostream_iterator<std::decay<decltype(*container.begin())>::type>(std::cout, " ")); std::cout << '\n'; };
// Example 1: Handcrafted -------------------------------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Search for comma, then take the part and add to the result
for (size_t i{ 0U }, startpos{ 0U }; i <= stringToSplit.size(); ++i) {
// So, if there is a comma or the end of the string
if ((stringToSplit[i] == ',') || (i == (stringToSplit.size()))) {
// Copy substring
c.push_back(stringToSplit.substr(startpos, i - startpos));
startpos = i + 1;
}
}
print(c);
}
// Example 2: Using very old strtok function ----------------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Split string into parts in a simple for loop
#pragma warning(suppress : 4996)
for (char* token = std::strtok(const_cast<char*>(stringToSplit.data()), ","); token != nullptr; token = std::strtok(nullptr, ",")) {
c.push_back(token);
}
print(c);
}
// Example 3: Very often used std::getline with additional istringstream ------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c{};
// Put string in an std::istringstream
std::istringstream iss{ stringToSplit };
// Extract string parts in simple for loop
for (std::string part{}; std::getline(iss, part, ','); c.push_back(part))
;
print(c);
}
// Example 4: Most flexible iterator solution ------------------------------------------------
{
// Our string that we want to split
std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
Container c(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});
//
// Everything done already with range constructor. No additional code needed.
//
print(c);
// Works also with other containers in the same way
std::forward_list<std::string> c2(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});
print(c2);
// And works with algorithms
std::deque<std::string> c3{};
std::copy(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {}, std::back_inserter(c3));
print(c3);
}
return 0;
}
请启用 C++17 进行编译。
可惜没人会看。