Reputation: 21
Here is the list I am working with:
Painting,150.10,10 Lamp,100.20,10 Rug,200.00,10 Clock,100.00,10 Sculpture,300.00,10 Photograph,200.00,10 Pottery,100.00,10 Watch,300.00,10 Wedgwood,70.00,10 Tiles,500.00,10
I have came up with this so far to seperate the items and am getting errors:
ifstream file("antiquelist.txt");
string name;
float price;
int quantity;
while(file.good()){
getline(file, name, ',', price, ',', quantity);
cout << name << endl;
Upvotes: 2
Views: 511
Reputation: 15265
Nobody will be interested in the following, since this answer is given 5 days after the question has been asked.
Additionally, the OP is new to C++ wnd will not understand it. But since this questions is tagged with C++, and the other answers are very C-Style-ish, I want to show here a more-modern C++ solution with an object oriented approach.
And, I see everywhere the abuse of the std::getline
for splitting strings, which is unbelievable, since a dedicated functions exist for splitting strings. The intended use for std::getline
is to "get a line" from a stream. As it name suggests. People fiddle around with this function and searching for delimiters and so on, but in my very humble opinion, we should not do this.
Since roundabout 10 years we have a dedicated, special C++ functionality for splitting strings into tokens, explicitely designed for this purpose. The std::sregex_token_iterator
. And because we have such a dedicated function, we should simply use it.
The idea behin it is the iterator concept. In C++ we have many containers and always iterators, to iterate over the similar elements in these containers. And a string, with similar elements (tokens), separated by a delimiter, can also be seen as such a container. And with the std::sregex:token_iterator
, we can iterate over the elements/tokens/substrings of the string, splitting it up effectively.
This iterator is very powerfull and you can do really much much more fancy stuff with it. But that is too much for here. Important is that splitting up a string into tokens is a one line. For example a variable definition using a range constructor for iterating over the tokens. See for example:
// The delimiter
const std::regex delimiter{ "," };
// Test data
std::string csvData("d1,d2,d3,d4,d5");
// Split the string
std::vector<std::string> tokens(std::sregex_token_iterator(csvData.begin(), csvData.end(), delimiter, -1), {});
Ant since is an iterator, you can use it with many algorithms. You should really stduy and use it. And for all those people, with big concerns about efficiency, please note, that in 90% of the cases only a few lines will be parsed. No problem.
So, next. We have an object oriented language. So lets use it and define a class for your data, with the data mebers mentioned by you, and additionally functions or operators, to operate on this data members. Only the class should know about its internals. Outside, we want to use it, without knowing the implementation.
In the example below the extractor and iserter will be overwritten, to enable stream compliant IO operations. In the extractor we will use also a regex
feature and will check exactly, if the input string matches our expectations. For this we use a std::regex
which exactly defines the data pattern in the CSV string. Then, if we found a match, we can use the data. So, this is not only splitting up the string, but also input validation. The function for this is: std::regex_match
.
And, if you look in main
, reading and parsing all CSV data is a one-liner. Same for the data output.
Please see:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <iterator>
#include <algorithm>
#include <regex>
const std::regex re{R"(^\s*?(\b\w+)\s*?,\s*?(\d+\.\d+)\s*?,\s*?(\d+)\s*?$)"};
// Proxy class for easy input and output
struct MyData {
std::string name{};
double price{};
long quantity{};
// Overwrite extractor operator for easier input
friend std::istream& operator >> (std::istream& is, MyData& md) {
// Read a complete line from your CSV file
if (std::string line{}; std::getline(is, line)) {
// Check, if the input string matches to pour expectation, so split and validate
if (std::smatch match{}; regex_match(line, match, re)) {
// Match is perfect. Assign the values to our internal data members
md.name = match[1]; md.price = std::stod(match[2]); md.quantity = std::stol(match[3]);
}
else // No match. Set everything to default
md = {};
}
return is;
}
// Simple output of our data members
friend std::ostream& operator << (std::ostream& os, const MyData& md) {
return os << "Name: " << md.name << "\t\tPrice: " << md.price << "\tQuantity: " << md.quantity;
}
};
int main() {
// Open the csv File and check, if it could be opened and everything is ok
if (std::ifstream csvStream("r:\\antiquelist.txt"); csvStream) {
// Read and parse the complete source file
std::vector myData(std::istream_iterator<MyData>(csvStream), {});
// Show complete result to user
std::copy(myData.begin(), myData.end(), std::ostream_iterator<MyData>(std::cout, "\n"));
}
else {
std::cerr << "\n*** Error: Could not open input file\n";
}
return 0;
}
Please look here, to get a better understanding about regex
What a pity that nobody will read this . . .
Upvotes: 1
Reputation: 84599
There are a number of ways to approach parsing the values from the line into the three values you want std::string item
, double price
, int qty
. You can take several routes, (1.) read directly from the file stream, or (2.) create a std::stringstream
from each line and read the values from the line. The difference being the need to remove the '\n'
left in the input-stream if reading directly from your file-stream.
By far the most robust would be to create a stringstream so what remains in your input-stream doesn't depend on what is read and ignored from the file-stream if you attempt to parse directly from the file-stream.
The basic approach is to declare a string to hold each line and opening your file (and validating the file is open for reading), e.g.
std::string s;
std::ifstream f (argv[1]); /* open file stream */
if (!f.good()) { /* validate file stream state good */
std::cerr << "error: file open failed.\n";
return 1;
}
Next loop, reading each line and creating a stringstream from each line. How you parse the value from the stringstream can take a couple of different routes, but simply validating the read of each value all at once avoid reading into temporary strings and then converting to double
and int
which would need separate try {...} catch() {..}
exception handling.
The basic approach is to read the item using getline (...,',')
with a ','
delimiter which will read everything up to the first ','
discarding the ','
. You can then read your price
directly. Character extraction reading price will stop at ','
which you can read using getline (...,',')
into a temporary variable (you could just read a char
, but that would fail if there was any whitespace between the end of price
and the ','
. Lastly, just read qty
directly. (validating that each read succeeds)
while (getline (f, s)) { /* read each line into s */
int qty;
double price;
std::string item, tmp;
std::stringstream ss (s); /* create stringstream from s */
/* read all 3 values from stringstream into separate values */
if (getline(ss,item,',') && ss >> price && getline(ss,tmp,',') && ss >> qty) {
std::cout << "item: " << std::left << std::setw(10) << item
<< " price: " << std::right << std::setw(5)
<< std::fixed << std::setprecision(1) << price
<< " qty: " << qty << '\n';
}
else /* if 3 values not read/handle error */
std::cerr << "invalid format: '" << s << "'\n";
}
Putting altogether into an example you would have:
#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <string>
int main (int argc, char **argv) {
if (argc < 2) { /* validate at least 1 argument for filename */
std::cerr << "usage: " << argv[0] << " filename\n";
return 1;
}
std::string s;
std::ifstream f (argv[1]); /* open file stream */
if (!f.good()) { /* validate file stream state good */
std::cerr << "error: file open failed.\n";
return 1;
}
while (getline (f, s)) { /* read each line into s */
int qty;
double price;
std::string item, tmp;
std::stringstream ss (s); /* create stringstream from s */
/* read all 3 values from stringstream into separate values */
if (getline(ss,item,',') && ss >> price && getline(ss,tmp,',') && ss >> qty) {
std::cout << "item: " << std::left << std::setw(10) << item
<< " price: " << std::right << std::setw(5)
<< std::fixed << std::setprecision(1) << price
<< " qty: " << qty << '\n';
}
else /* if 3 values not read/handle error */
std::cerr << "invalid format: '" << s << "'\n";
}
}
Example Use/Output
$ ./bin/readartitems dat/antiquelist.txt
item: Painting price: 150.1 qty: 10
item: Lamp price: 100.2 qty: 10
item: Rug price: 200.0 qty: 10
item: Clock price: 100.0 qty: 10
item: Sculpture price: 300.0 qty: 10
item: Photograph price: 200.0 qty: 10
item: Pottery price: 100.0 qty: 10
item: Watch price: 300.0 qty: 10
item: Wedgwood price: 70.0 qty: 10
item: Tiles price: 500.0 qty: 10
Look things over and let me know if you have further questions.
Upvotes: 2
Reputation: 877
You need to get new lines of data via getline with the default tokenizer parameter, and then you parse each line of data again with getline using the comma ',' as the tokenizer parameter:
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <fstream>
using namespace std;
struct Data {
string m_name;
float m_price;
int m_quantity;
};
int main() {
ifstream file("antiquelist.txt");
string line;
std::vector<Data> items;
Data rowData;
while (getline(file, line)) {
stringstream splitter(line);
string dataStr;
getline(splitter, dataStr, ',');
rowData.m_name = dataStr;
getline(splitter, dataStr, ',');
stringstream(dataStr) >> rowData.m_price;
getline(splitter, dataStr);
stringstream(dataStr) >> rowData.m_quantity;
items.push_back(rowData);
}
return 0;
}
Upvotes: 0