我尝试在代码中用 cmets 来描述这个解决方案。它基本上只是定义了流操作符,以便能够从任何流中读取/写入数据。
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <ios>
#include <sstream>
#include <stdexcept>
// create an alias for a number of events using a standard container
// for int's, std::vector<int>
using EventList = std::vector<int>;
// add an input stream operator for our EventList to be able to read
// and split strings looking like [int,int,...,int]
std::istream& operator>>(std::istream& is, EventList& el) {
std::string tmp; // a temporary string
el.clear(); // remove any existing events
if(std::getline(is, tmp)) { // read until end-of-line
// check that we got [ and ]
if(tmp.size()<2 || tmp[0] != '[' || tmp[tmp.size()-1] != ']') {
// wrong format, set the input streams failbit
is.setstate(std::ios::failbit);
} else {
// remove the first and last character [ and ]
tmp = tmp.substr(1, tmp.size()-2);
// put the string in a string stream to be able
// to use std::getline with a delimiter
std::stringstream ss(tmp);
// loop until the stringstream is empty
while( std::getline(ss, tmp, ',') ) {
// tmp should now be a string of digits
// use stoi to convert the string to an int
try {
int an_int = std::stoi(tmp);
// put the int at the back of our EventList
el.emplace_back(an_int);
} catch(const std::invalid_argument& ex) {
// the conversion to an int failed
// set the input streams failbit
is.setstate(std::ios::failbit);
}
}
}
} else {
// getline failed, set the failbit on the stream
is.setstate(std::ios::failbit);
}
return is;
}
// add an output stream operator for EventList to be able to output
// it in the same format as it was read
std::ostream& operator<<(std::ostream& os, const EventList& el) {
os << "[";
if(el.size()) { // check that we have events to stream
// create an iterator that points at the first element
EventList::const_iterator it = el.begin();
// dereference the iterator to get the int it points to
// and stream that int
os << *it;
++it; // step to the next event
// loop until the iterator points beyond the last element
// in the EventList
for(;it!=el.end(); ++it) {
// prepend each event with a comma
os << "," << *it;
}
}
os << "]";
return os;
}
// here's an operator for a vector of EventList's for convenience
// it follows the same pattern as the operator above
std::ostream& operator<<(std::ostream& os, const std::vector<EventList>& vel) {
os << "[";
if(vel.size()) {
std::vector<EventList>::const_iterator it = vel.begin();
os << *it;
++it;
for(;it!=vel.end(); ++it) {
os << " " << *it;
}
}
os << "]";
return os;
}
// one line in your file int,int[int...]
// broken down into a struct
struct OneDataLine {
int budget;
int hotelType;
EventList events; // using the alias created above
// the default constructor with initialization of members
OneDataLine() :
budget(0),
hotelType(0),
events()
{}
// declaring stream operators as friends, allowing them to operate
// on the objects members. this is not really necessary
// since this struct has all members public but if you later decide
// to make them private, this will come in handy
friend std::istream& operator>>(std::istream&, OneDataLine&);
friend std::ostream& operator<<(std::ostream&, const OneDataLine&);
};
// an input stream operator for reading one data line
std::istream& operator>>(std::istream& is, OneDataLine& d) {
char separator;
is >> d.budget >> separator >> d.hotelType;
if(separator != ',') {
// if the separator between budget and hotelType is not
// a comma, set the input stream in a failed state
is.setstate(std::ios::failbit);
} else {
// we should now only have the events part left on
// the line. stream it to the EventList for which we
// have already added an input stream operator
is >> d.events;
}
return is;
}
// an output stream operator for writing one data line
std::ostream& operator<<(std::ostream& os, const OneDataLine& d) {
// int's have built-in stream operators
// and we have also added an output stream operator for
// EventList so streaming becomes easy
os << d.budget << "," << d.hotelType << d.events;
return os;
}
// USAGE: progname datafile1 ... datafileX
int main(int argc, char* argv[]) {
// convert C style main() parameters to a C++ container.
// we use std::vector again, but this time for storing
// strings
std::vector<std::string> args(argv+1, argv+argc);
// yet another vector, but this is for storing data lines
std::vector<OneDataLine> all_data_lines;
// Reading part
// loop over the input parameters to main()
for(const std::string& file : args) {
std::fstream fs(file); // open file for reading
// loop over the opened file for as long as the
// filestream's failbit isn't set
while(fs) {
// a temporary instance of OneDataLine
OneDataLine tmp;
// stream from the open file to our temporary
fs >> tmp;
// if the failbit still isn't set, move the
// content of the temporary variable into
// our vector of data lines
if(fs) all_data_lines.emplace_back(std::move(tmp));
}
// the filestream will be automatically closed
// when it goes out of scope
}
// Writing part
// loop over all the data lines we've collected and
// stream to cout. we could just as well stream to
// a file opened for writing
for(const OneDataLine& line : all_data_lines) {
// stream the complete data line using our own output
// stream operator for OneDataLine
std::cout << line << "\n";
// stream individual members too
std::cout << " budget : " << line.budget << "\n";
std::cout << " hotelType: " << line.hotelType << "\n";
std::cout << " events : " << line.events << "\n";
// and stream each event separately
std::cout << " [\n";
for(const int& ev : line.events) {
std::cout << " " << ev << "\n";
}
std::cout << " ]\n";
}
// Creating containers for each category
std::vector<int> budgets;
std::vector<int> hotelTypes;
std::vector<EventList> event_lists;
// loop through the collected data and put each member in
// the container for its category
for(const OneDataLine& line : all_data_lines) {
budgets.push_back(line.budget);
hotelTypes.push_back(line.hotelType);
event_lists.push_back(line.events);
}
// Output categorized containers
// here we use EventList's (std::vector<int>'s) output stream operator
std::cout << "budgets : " << budgets << "\n";
std::cout << "hotelTypes : " << hotelTypes << "\n";
// and here we use our convenience output stream operator for
// a vector of EventList
std::cout << "event_lists: " << event_lists << "\n";
return 0;
}
样本输出:
% progname datafile
[...skipping to the end...]
7484,4[11,5,14,2,6,7,8,1,0]
budget : 7484
hotelType: 4
events : [11,5,14,2,6,7,8,1,0]
[
11
5
14
2
6
7
8
1
0
]
budgets : [9020,7805,7075,7679,6356,6874,4715,4784,4321,6469,4838,4103,5904,5775,7070,4605,7484]
hotelTypes : [4,5,5,4,3,5,4,5,3,5,4,3,5,3,4,3,4]
event_lists: [[2,0,5,14,10,4,3,13,1] [13,3,12,12,0,9,7,10,6,1] [3,2,4,9,7,0,1,5,6,14] [0,4,14,1,3,12,5,10] [7,3] [14,0,4,10,9,3] [9] [11] [5,3,8,9] [7,6,6,14,12,5,2] [1,2] [14] [5,4,6] [10,14,14,8,7,3,4] [1,4,6,11,13,3,2,5,14] [6,10,1,8,7,3,3] [11,5,14,2,6,7,8,1,0]]