Parse string of numbers into vector of structs - c++

I want to parse a string of numbers into a vector of elements. The string consists of blocks of four numbers, separated by ( ) : /, and each block is separated by a ;.
Specifically, the string is in this format: int(int):float/float;, see code sample below. I think I could use a regular expression, but since the data is so structured, I'm sure there must be a more approachable and easier way to parse such a string. I'm using istringstream, but it feels a bit clumsy.
std::string line = "0(0):0/0;1(2):0.01/0.02;2(4):0.02/0.04;3(6):0.03/0.06;"
struct Element {
int a;
int b;
int c;
int d;
};
std::vector<Element> = parse(line);
std::vector<Element> parse(std::string line)
{
std::vector<Element> elements;
std::istringstream iss(line);
while(iss) {
char dummy;
Element element;
iss >> element.a;
iss.read(&dummy,sizeof(dummy)); // (
iss >> element.b;
iss.read(&dummy,sizeof(dummy)); // )
iss.read(&dummy,sizeof(dummy)); // :
iss >> element.c;
iss.read(&dummy,sizeof(dummy)); // /
iss >> element.d;
iss.read(&dummy,sizeof(dummy)); // ;
if (!iss) {break;}
elements.push_back(element);
}
return elements;
}
My questions:
What would be a good way to parse? Should I use std::stringstream and read in number by number and 'chop off' the characters in between? As done in the code sample?
This code has a bug and attempts to read one extra set of values, because while(iss) is still true, after the last character has been read in. How to terminate this loop without testing after each iss>>? Or more generally, how to loop over extractions from istringstream?

Your data are well structured, you can easily overload operator>> to extract the class members from an std::ifstream and then keep reading them from an istringstream or a file stream.
Here is a possible implementation:
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <fstream>
#include <iterator>
#include <stdexcept>
class Element
{
public:
Element() {}
Element(int aa, int bb, float cc, float dd) : a{aa}, b{bb}, c{cc}, d{dd} {}
friend std::istream &operator>> (std::istream &in, Element &e);
friend std::ostream &operator<< (std::ostream &out, Element const &e);
private:
int a;
int b;
float c;
float d;
};
std::istream &operator>> (std::istream &in, Element &e)
{
char delimiter;
if ( not ( in >> e.a >> delimiter and delimiter == '(' and
in >> e.b >> delimiter and delimiter == ')' and
in >> delimiter and delimiter == ':' and
in >> e.c >> delimiter and delimiter == '/' and
in >> e.d >> delimiter and delimiter == ';' )
and not in.eof() )
{
in.setstate(std::ios_base::failbit);
}
return in;
}
std::ostream &operator<< (std::ostream &out, Element const &e)
{
return out << e.a << '(' << e.b << "):" << e.c << '/' << e.d << ';';
}
std::vector<Element> read_Elements_from(std::istream &in)
{
std::vector<Element> tmp (
std::istream_iterator<Element>{in},
std::istream_iterator<Element>{}
);
if ( not in.eof() )
throw std::runtime_error("Wrong format");
return tmp;
}
int main()
{
try
{
using std::cout;
std::istringstream iss {
"0(0):0/0;1(2):0.01/0.2;2(4):0.02/0.04;3(6):0.03/0.06;"
};
auto els_s = read_Elements_from(iss);
cout << "Elements read from the string:\n";
for ( auto const &i : els_s )
{
cout << i << '\n';
}
// assuming a file which lines are similar to the string provided
std::ifstream input_file {"input_data.txt"};
if ( not input_file )
throw std::runtime_error("Can't open input file");
auto els_f = read_Elements_from(input_file);
cout << "\nElements read from the file:\n";
for ( auto const &i : els_f )
{
cout << i << '\n';
}
}
catch ( std::exception const &e )
{
std::cerr << "\nAn unexpected problem cause this application to end:\n\n"
<< e.what() << ".\n\n";
return EXIT_FAILURE;
}
}

Related

C++ program to perform operations using a text file

item,price,qty,ordno,trdno
abc,54,2,123,32
xyz,34,2,345,21
item: string (char[])
price,qty (int)
ordno (long long)
trdno (int)
Make a structure for above mentioned fields
Make a vector (array, or any other container type) to hold multiple instances of this structure
1: Read file
2: read line, split values
3: initialize above mentioned structure object
4: add this object of structure to container
5: when whole file is read.. iterate over the container and print each elements values (serialno, orderno, tradeno, price, qty, item)
I tried this and it is not working-
#include<bits/stdc++.h>
using namespace std;
struct item {
string name;
double price;
int quantity;
int order_no;
int trd_no;
};
int main()
{
int n;cin>>n;
string str, T;
ifstream read("input.txt");
while(getline(read,str))
{
cout<<str<<endl;
}
stringstream X(str); // X is an object of stringstream that references the S string
cout<<endl;
while (getline(X, T, ','))
{
cout << T << endl; // print split string
}
read.close();
return 0;
}
For the code that you are showing, you misplaced just one '}' after the first while. So the code will read in the first while-loop all lines of the file and is then empty. And, then you try to split the lines. This can of course not work.
If you move the closing bracket to in front of read.close(); then your code will work. Like this:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
using namespace std;
struct item {
string name;
double price;
int quantity;
int order_no;
int trd_no;
};
int main()
{
int n; cin >> n;
string str, T;
ifstream read("input.txt");
while (getline(read, str))
{
cout << str << endl;
stringstream X(str); // X is an object of stringstream that references the S string
cout << endl;
while (getline(X, T, ','))
{
cout << T << endl; // print split string
}
}
read.close();
return 0;
}
Caveat: this will not work, if the source file contains the header row! You can read this and throw it away, if needed.
If we follow the instruction of your homework, line by line, and assume that the first line contains header rows, then we would do like the following.
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
struct Item {
std::string name;
double price;
int quantity;
long long order_no;
int trd_no;
};
int main()
{
std::vector<Item> items;
std::string str, T;
std::ifstream read("input.txt");
// Read first line and ignore it
std::getline(read, str);
while (std::getline(read, str))
{
std::istringstream X(str);
Item tempItem;
getline(X, T, ',');
tempItem.name = T;
getline(X, T, ',');
tempItem.price = std::stod(T);
getline(X, T, ',');
tempItem.quantity = std::stoi(T);
getline(X, T, ',');
tempItem.order_no = std::stoll(T);
getline(X, T, ',');
tempItem.trd_no = std::stoi(T);
items.push_back(tempItem);
}
read.close();
for (const Item& item : items)
std::cout << item.name << ' ' << item.price << ' ' << item.quantity << ' '
<< item.order_no << ' ' << item.trd_no << '\n';
}
And, with a little bit more advanced C++, where we especially keep data and methods in a class, we could do the following:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <iomanip>
#include <algorithm>
#include <iterator>
// The item
struct Item {
std::string name{};
double price{};
int quantity{};
long long order_no{};
int trd_no{};
// Overwrite extraction operator for easier stream IO
friend std::istream& operator >> (std::istream& is, Item& i) {
char c;
return std::getline(is >> std::ws, i.name, ',') >> i.price >> c >> i.quantity >> c >> i.order_no >> c >> i.trd_no;
}
// Overwrite inserter for easier output
friend std::ostream& operator << (std::ostream& os, const Item& i) {
return os << "Name: " << i.name << "\tPrice: " << i.price << "\tQuantity: " << i.quantity << "\tOrder no: " << i.order_no << "\tTRD no: " << i.trd_no;
}
};
// The Container
struct Data {
std::vector<Item> items{};
// Overwrite extraction operator for easier stream IO
friend std::istream& operator >> (std::istream& is, Data& d) {
// Read header line and ignore it
std::string dummy; std::getline(is, dummy);
// Delete potential old data
d.items.clear();
// Read all new data from file
std::copy(std::istream_iterator<Item>(is), {}, std::back_inserter(d.items));
return is;
}
// Overwrite inserter for easier output
friend std::ostream& operator << (std::ostream& os, const Data& d) {
std::copy(d.items.begin(), d.items.end(), std::ostream_iterator<Item>(os, "\n"));
return os;
}
};
int main()
{
// Open file and check, if it could be opened
if (std::ifstream sourceFileStream("input.txt"); sourceFileStream) {
// Define container
Data data{};
// Read and parse complete source file and assign to data
sourceFileStream >> data;
// Show result
std::cout << data;
}
else std::cerr << "\n\nError: Could not open source file:\n\n";
}

How do I include an int inside a getline();

Just a simple question, how do I include int values inside a getline()? I have searched online but couldn't find any that helps me. I am reading off a txt file. It is a row of numbers. For eg: 1,2,3,4,5. I am hoping that I can apply these int values anywhere so that the only way I can change the values is through the txt file.
I decided to use getline() but realise that I cannot use an integer. I am sorry, I am new to this C++. I hope that you can tell me where I went wrong.
Thanks!
This is my struct:
struct vacancyData {
int CCSpot;
int SNSpot;
int TPSpot;
int SCSpot;
int DRSpot;
};
This is my code:
ifstream File2;
File2.open("Vacancy.txt");
vector<vacancyData> v1;
vacancyData f;
while (getline(File2, f.CCSpot, ','))
{
getline(File2, f.SNSpot, ',');
getline(File2, f.TPSpot, ',');
getline(File2, f.SCSpot, ',');
getline(File2, f.DRSpot, '\n');
v1.push_back(f);
}
The direct answer to the question
How do I include an int inside a getline()
is: This is not possible at all.
std::getline is basically used to read a std::string from a stream, until a delimiter is found. In most cases, and that is also a default argument, the delimiter is '\n'. And with that, a complete line is read into a std::string. Please read here for a description.
If your input data is OK in most cases, then no std::getline is needed. Basic input validation can also be done directly. Please see the below example code:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
struct VacancyData {
int CCSpot;
int SNSpot;
int TPSpot;
int SCSpot;
int DRSpot;
};
int main() {
// Filename
const std::string fileName{ "r:\\vacancy.txt" };
// Open file and check, if it could be opened. Use C++17 if-statement with initializer
if (std::ifstream vacancyFileStream{ fileName }; vacancyFileStream) {
// Here we will stor our data
std::vector<VacancyData> all{};
// Temps, to check, if delimiter is comma
char c1{}, c2{}, c3{}, c4{};
// This is one for loop and will read the complet file and do basic input validation
for (VacancyData vc{};
(vacancyFileStream >> vc.CCSpot >> c1 >> vc.SNSpot >> c2 >> vc.TPSpot >> c3 >> vc.SCSpot >> c4 >> vc.DRSpot) &&
c1 == ',' && c2 == ',' && c3 == ',' && c4 == ',';
all.push_back(vc))
; // Empt loop body. No statement within for loop body
// Ws there an error and could all data be read?
if (vacancyFileStream.fail() || not vacancyFileStream.eof())
std::cerr << "\n\nErorw hile reading input data\n\n";
for (const VacancyData& vc : all)
std::cout << vc.CCSpot << '\t' << vc.SNSpot << '\t' << vc.TPSpot << '\t' << vc.SCSpot << '\t' << vc.DRSpot << '\n';
}
else std::cerr << "\n\nEror: cannot open source file '" << fileName << "'\n\n";
return 0;
}
But, it is considere good practice to first read a complete line, then split it up in parts and then convert it to your structure elements.
Additionally: In C++ we often use an object oriented approach. Meaning, and objects methods, operating with the data are encapsulated in a class/struct.
In your case, we would overwrite the extractor >> and inserter operator <<.
This would then look like that (I am using C++17):
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <iterator>
#include <sstream>
struct VacancyData {
int CCSpot;
int SNSpot;
int TPSpot;
int SCSpot;
int DRSpot;
friend std::istream& operator >> (std::istream& is, VacancyData& vc) {
char c{};
return is >> vc.CCSpot >> c >> vc.SNSpot >> c >> vc.TPSpot >> c >> vc.SCSpot >> c >> vc.DRSpot;
}
friend std::ostream& operator << (std::ostream& os, const VacancyData& vc) {
return os << vc.CCSpot << '\t' << vc.SNSpot << '\t' << vc.TPSpot << '\t' << vc.SCSpot << '\t' << vc.DRSpot;
}
};
int main() {
// Filename
const std::string fileName{ "r:\\vacancy.txt" };
// Open file and check, if it could be opened. Use C++17 if-statement with initializer
if (std::ifstream vacancyFileStream{ fileName }; vacancyFileStream) {
// Here we will store our data. Read complete file and parse it
std::vector all(std::istream_iterator< VacancyData>(vacancyFileStream), {});
// Ws there an error and could all data be read?
if (vacancyFileStream.fail() || not vacancyFileStream.eof())
std::cerr << "\n\nErorw hile reading input data\n\n";
// Show output
for (const VacancyData& vc : all)
std::cout << vc << '\n';
}
else std::cerr << "\n\nEror: cannot open source file '" << fileName << "'\n\n";
return 0;
}
Now, if you want to change the reading of your data, you can use any method that you want. For example, you can read a complete line, using std::getline use any method shown below to split the input string into parts. Then you can use any method to convert that string parts into an integer for your struct. You will just change the body of your extractor operator. Nothing else will be affected. That sis the beauty of encapsulation.
Regarding: Splitting a string into tokens is a very old task. There are many many solutions available. All have different properties. Some are difficult to understand, some are hard to develop, some are more complex, slower or faster or more flexible or not.
Alternatives
Handcrafted, many variants, using pointers or iterators, maybe hard to develop and error prone.
Using old style std::strtok function. Maybe unsafe. Maybe should not be used any longer
std::getline. Most used implementation. But actually a "misuse" and not so flexible
Using dedicated modern function, specifically developed for this purpose, most flexible and good fitting into the STL environment and algortithm landscape. But slower.
Please see 4 examples in one piece of code.
#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;
}
In modern C++ you would probably do:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <iterator>
#include <sstream>
#include <regex>
// Regex for integer
const std::regex re{R"(([-+]?\d+))"};
struct VacancyData {
int CCSpot;
int SNSpot;
int TPSpot;
int SCSpot;
int DRSpot;
friend std::istream& operator >> (std::istream& is, VacancyData& vc) {
// Read a complete line and check, if that worked
if (std::string line{}; std::getline(is, line)) {
// Split the string into parts. The parts will definitely contain a integer
std::vector part(std::sregex_token_iterator(line.begin(), line.end(), re), {});
// Sanity check. Could we read 5 values?
if (not (part.size() == 5u)) {
std::cerr << "\n\nError while parsing line '" << line << '\n';
is.setstate(std::ios::failbit);
}
else {
// Splitting the string worked. We will have intergers in the parts. stoi will not fail
vc.CCSpot = std::stoi(part[0]);
vc.SNSpot = std::stoi(part[1]);
vc.TPSpot = std::stoi(part[2]);
vc.SCSpot = std::stoi(part[3]);
vc.DRSpot = std::stoi(part[4]);
}
}
return is;
}
friend std::ostream& operator << (std::ostream& os, const VacancyData& vc) {
return os << vc.CCSpot << '\t' << vc.SNSpot << '\t' << vc.TPSpot << '\t' << vc.SCSpot << '\t' << vc.DRSpot;
}
};
int main() {
// Filename
const std::string fileName{ "r:\\vacancy.txt" };
// Open file and check, if it could be opened. Use C++17 if-statement with initializer
if (std::ifstream vacancyFileStream{ fileName }; vacancyFileStream) {
// Here we will store our data. Read complete file and parse it
std::vector all(std::istream_iterator< VacancyData>(vacancyFileStream), {});
// Show output
for (const VacancyData& vc : all)
std::cout << vc << '\n';
}
else std::cerr << "\n\nEror: cannot open source file '" << fileName << "'\n\n";
return 0;
}
But, there are tons of different possible solutions. And everybody can select whatever.
All above needs to be compiles with C++17.
You can use std::getline to do this, but you need use a std::string and then to convert it to an integer. One way is to use the std::stoi function.
Example:
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
struct vacancyData {
int CCSpot;
int SNSpot;
int TPSpot;
int SCSpot;
int DRSpot;
};
// Overload operator>> for reading a "vacancyData" struct from an istream (like a file)
std::istream& operator>>(std::istream& is, vacancyData& vd) {
std::string tmp;
try {
if(std::getline(is, tmp, ',')) {
vd.CCSpot = std::stoi(tmp);
if(std::getline(is, tmp, ',')) {
vd.SNSpot = std::stoi(tmp);
if(std::getline(is, tmp, ',')) {
vd.TPSpot = std::stoi(tmp);
if(std::getline(is, tmp, ',')) {
vd.SNSpot = std::stoi(tmp);
if(std::getline(is, tmp, ',')) {
vd.SCSpot = std::stoi(tmp);
if(std::getline(is, tmp)) {
vd.DRSpot = std::stoi(tmp);
}
}
}
}
}
}
}
catch(...) { // one of the stoi calls failed
is.setstate(std::ios::failbit);
}
return is;
}
int main() {
std::ifstream File2("Vacancy.txt");
if(File2) {
// construct the vector using iterators:
std::vector<vacancyData> v1(std::istream_iterator<vacancyData>(File2),
std::istream_iterator<vacancyData>{});
// use the filled vector "v1" ...
}
}
But since there is built-in support for extracting ints directly from istreams, I suggest using that instead.
Example:
// A small support class to "eat" separators, like comma and newline
struct eater { char ch; };
std::istream& operator>>(std::istream& is, eater& e) {
if(is.peek() == e.ch) is.ignore(); // if the next char is the expected, skip it
else is.setstate(std::ios::failbit); // else set the failbit
return is;
}
std::istream& operator>>(std::istream& is, vacancyData& vd) {
eater comma{','};
eater newline{'\n'};
return is >>
vd.CCSpot >> comma >>
vd.SNSpot >> comma >>
vd.TPSpot >> comma >>
vd.SCSpot >> comma >>
vd.DRSpot >> newline;
}

how can I input a literal string into istream? [duplicate]

How to achieve scanf("%d # %d",&a,&b);sort of effect with cin in C++ ?
You can skip the # by extracting it into a character:
std::istringstream iss("10 # 20");
int main()
{
int a, b; char hash;
iss >> a >> hash >> b;
assert(a == 10 && b == 20);
}
You could create your own stream manipulator. It is fairly easy.
#include <ios>
#include <iostream>
using namespace std;
// skips the number of characters equal to the length of given text
// does not check whether the skipped characters are the same as it
struct skip
{
const char * text;
skip(const char * text) : text(text) {}
};
std::istream & operator >> (std::istream & stream, const skip & x)
{
ios_base::fmtflags f = stream.flags();
stream >> noskipws;
char c;
const char * text = x.text;
while (stream && *text++)
stream >> c;
stream.flags(f);
return stream;
}
int main()
{
int a, b;
cin >> a >> skip(" # ") >> b;
cout << a << ", " << b << endl;
return 0;
}
There isn't a direct function inside the istream class that mimics it, unfortunately. There are functions that you might be able to use to manipulate the stream and get the correct input, but I'm not familiar with how they work so I couldn't tell you how.
My best suggestion on how I would personally do it is to use getline() to put the input into a string and then from there I would do a few checks to see if it matches the format. So in your case I would grab the first substring up until the first space, make sure it's a valid decimal, check to make sure the pound sign ('#') is in the correct spot, then grab the ending number to make sure it's valid. If any one of those three objects are incorrect I would set some boolean variable to false to kick out or return or something to indicate that the input was invalid and not the correct format.
Pseudo Code:
...
getline(cin,myStr);
while(!formatCheck(myStr))
{
cout<<"Not valid format for input";
getline(cin,myStr);
}
...
bool formatCheck(string str)
{
string firstPart=str.subString(0,firstSpaceLocation);
string middle=str[firstSpaceLocation+1];
string lastPart=str.subString(firstSpaceLocation+3,end);
if(first part not a valid number || middle!="#" || last part not a valid number)
{
return false;
}
return true;
}
Here's another way. You can classify # as a whitespace character through the std::ctype<char> facet imbued in the locale:
#include <iostream>
#include <sstream>
#include <vector>
namespace detail
{
enum options { add, remove };
class ctype : public std::ctype<char>
{
private:
static mask* get_table(const std::string& ws, options opt)
{
static std::vector<mask> table(classic_table(),
classic_table() + table_size);
for (char c : ws)
{
if (opt == add)
table[c] |= space;
else if (opt == remove)
table[c] &= ~space;
}
return &table[0];
}
public:
ctype(const std::string& ws, options opt)
: std::ctype<char>(get_table(ws, opt)) { }
};
}
class adjustws_impl
{
public:
adjustws_impl(const std::string& ws, detail::options opt) :
m_ws(ws),
m_opt(opt)
{ }
friend std::istream& operator>>(std::istream& is,
const adjustws_impl& manip)
{
is.imbue(std::locale(is.getloc(),
new detail::ctype(manip.m_ws, manip.m_opt)));
return is;
}
private:
std::string m_ws;
detail::options m_opt;
};
adjustws_impl setws(const std::string& ws)
{
return adjustws_impl(ws, detail::add);
}
adjustws_impl unsetws(const std::string& ws)
{
return adjustws_impl(ws, detail::remove);
}
int main()
{
std::istringstream iss("10 # 20");
int a, b;
iss >> setws("#");
iss >> a >> b;
iss >> unsetws("#");
std::cout << a << ' ' << b; // 10 20
}
You can skip the #, or any single character, by using std::istream::ignore
std::istringstream sstr("1024 # 768");
int main()
{
int a, b;
sstr >> a;
sstr.ignore(256,'#'); // ignore until hash character
sstr >> b;
std::cout << "a: " << a << " b: " << b << std::endl;
}

Parsing through text file with C++

I am trying to figure out the best way to read in numbers from a text file and set these numbers to variables. I am having trouble because there will be multiple text files that will be testing my code and they are all of different lengths and sizes. A sample test one looks like this:
0 (1,3) (3,5)
1 (2,6)
2 (4,2)
3 (1,1) (2,4) (4,6)
4 (0,3) (2,7)
Where the first number represents a vertex on a graph, the first number in a coordinate is the vertex it is going towards in a directed graph, and the second number is the weight of the edge. I tried doing getline and putting it into arrays but in certain test cases there could be 100 coordinates and I am not sure how to specify array size. I am also having trouble parsing through the parenthesis and comma and am not sure how to initialize the variables with the correct number from the text file.
Parsing shouldn't be that difficult, espacially when you can use std::stringstream to separate all the elements from input. Indeed, you want to remove all the paranthesis first then emplace the elements into the container.
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
#include <algorithm>
#include <cctype>
int main()
{
std::ifstream read("file.txt");
std::vector<std::vector<std::pair<int, int>>> graph;
// read until you reach the end of the file
for (std::string line; std::getline(read, line); ) {
// removing punctuation like paranthesis, commas, etc.
std::replace_if(std::begin(line), std::end(line), [] (char x) { return std::ispunct(x); }, ' ');
// inserting the line into a stream that helps us parse the content
std::stringstream ss(line);
// read the node number
int source, destination, weight;
ss >> source;
// create a new vector for the new node, so you can place all it's destinations / weights in it
graph.insert(std::next(std::begin(graph), source), {{}});
// read the dests / weights until you reach the end of the current line
while (ss >> destination >> weight)
graph[source].emplace_back(destination, weight);
}
read.close();
std::ofstream write("output.txt");
for (const auto node : graph) {
for (const auto [dest, weight] : node)
write << "(" << dest << ", " << weight << ") ";
write << '\n';
}
}
Note that you need C++17 to compile the code. You have to use basic loops instead of ranged-for loops and omit auto if you use an older C++ standard. Also I used a vector of pairs, but it's better if you use a struct / class for nodes, to make the code more maintanable.
This works too.
#include<iostream>
#include<sstream>
#include<string>
using namespace std;
int main() {
int vertices[1000][3], qv = 0; //use number more than 1000 if it is required
while (cin) {
int n;
char c;
string s;
getline(cin, s);
istringstream is(s);
is >> n;
is >> c;
while (c == '(') {
vertices[qv][0] = n;
is >> vertices[qv][1];
is >> c; //,
is >> vertices[qv++][2];
is >> c; //)
is >> c; //(
}
}
for (int i = 0; i < qv; i++) //unified view
cout << vertices[i][0] << ' ' << vertices[i][1] << ' ' << vertices[i][2] << endl;
for (int i = 0; i < qv; i++) { //initial view
cout << vertices[i][0];
cout << " (" << vertices[i][1] << "," << vertices[i][2] << ")";
while (i + 1 < qv && vertices[i][0] == vertices[i + 1][0]) {
i++;
cout << " (" << vertices[i][1] << "," << vertices[i][2] << ")";
}
cout << endl;
}
}
I would use something like this:
#include <string>
#include <sstream>
#include <fstream>
#include <vector>
#include <algorithm>
struct coordinate
{
int vertex;
int weight;
};
struct vertex_set
{
int vertex;
std::vector<coordinate> coordinates;
};
std::istream& operator>>(std::istream &in, coordinate &out)
{
char ch1, ch2, ch3;
if (in >> ch1 >> out.to_vertex >> ch2 >> out.weight >> ch3)
{
if ((ch1 != '(') || (ch2 != ',') || (ch3 != ')'))
in.setstate(std::ios_base::failbit);
}
return in;
}
std::istream& operator>>(std::istream &in, std::vector<coordinate> &out)
{
out.clear();
coordinate coord;
while (in >> coord)
out.push_back(coord);
return in;
}
std::istream& operator>>(std::istream &in, vertex_set &out)
{
return in >> out.vertex >> out.coordinates;
}
std::ifstream f("file.txt");
std::string line;
while (std::getline(f, line))
{
vertex_set vs;
if (std::istringstream(line) >> vs)
{
// use vs.vertex and vs.coordinates as needed...
}
}

Skipping expected characters like scanf() with cin

How to achieve scanf("%d # %d",&a,&b);sort of effect with cin in C++ ?
You can skip the # by extracting it into a character:
std::istringstream iss("10 # 20");
int main()
{
int a, b; char hash;
iss >> a >> hash >> b;
assert(a == 10 && b == 20);
}
You could create your own stream manipulator. It is fairly easy.
#include <ios>
#include <iostream>
using namespace std;
// skips the number of characters equal to the length of given text
// does not check whether the skipped characters are the same as it
struct skip
{
const char * text;
skip(const char * text) : text(text) {}
};
std::istream & operator >> (std::istream & stream, const skip & x)
{
ios_base::fmtflags f = stream.flags();
stream >> noskipws;
char c;
const char * text = x.text;
while (stream && *text++)
stream >> c;
stream.flags(f);
return stream;
}
int main()
{
int a, b;
cin >> a >> skip(" # ") >> b;
cout << a << ", " << b << endl;
return 0;
}
There isn't a direct function inside the istream class that mimics it, unfortunately. There are functions that you might be able to use to manipulate the stream and get the correct input, but I'm not familiar with how they work so I couldn't tell you how.
My best suggestion on how I would personally do it is to use getline() to put the input into a string and then from there I would do a few checks to see if it matches the format. So in your case I would grab the first substring up until the first space, make sure it's a valid decimal, check to make sure the pound sign ('#') is in the correct spot, then grab the ending number to make sure it's valid. If any one of those three objects are incorrect I would set some boolean variable to false to kick out or return or something to indicate that the input was invalid and not the correct format.
Pseudo Code:
...
getline(cin,myStr);
while(!formatCheck(myStr))
{
cout<<"Not valid format for input";
getline(cin,myStr);
}
...
bool formatCheck(string str)
{
string firstPart=str.subString(0,firstSpaceLocation);
string middle=str[firstSpaceLocation+1];
string lastPart=str.subString(firstSpaceLocation+3,end);
if(first part not a valid number || middle!="#" || last part not a valid number)
{
return false;
}
return true;
}
Here's another way. You can classify # as a whitespace character through the std::ctype<char> facet imbued in the locale:
#include <iostream>
#include <sstream>
#include <vector>
namespace detail
{
enum options { add, remove };
class ctype : public std::ctype<char>
{
private:
static mask* get_table(const std::string& ws, options opt)
{
static std::vector<mask> table(classic_table(),
classic_table() + table_size);
for (char c : ws)
{
if (opt == add)
table[c] |= space;
else if (opt == remove)
table[c] &= ~space;
}
return &table[0];
}
public:
ctype(const std::string& ws, options opt)
: std::ctype<char>(get_table(ws, opt)) { }
};
}
class adjustws_impl
{
public:
adjustws_impl(const std::string& ws, detail::options opt) :
m_ws(ws),
m_opt(opt)
{ }
friend std::istream& operator>>(std::istream& is,
const adjustws_impl& manip)
{
is.imbue(std::locale(is.getloc(),
new detail::ctype(manip.m_ws, manip.m_opt)));
return is;
}
private:
std::string m_ws;
detail::options m_opt;
};
adjustws_impl setws(const std::string& ws)
{
return adjustws_impl(ws, detail::add);
}
adjustws_impl unsetws(const std::string& ws)
{
return adjustws_impl(ws, detail::remove);
}
int main()
{
std::istringstream iss("10 # 20");
int a, b;
iss >> setws("#");
iss >> a >> b;
iss >> unsetws("#");
std::cout << a << ' ' << b; // 10 20
}
You can skip the #, or any single character, by using std::istream::ignore
std::istringstream sstr("1024 # 768");
int main()
{
int a, b;
sstr >> a;
sstr.ignore(256,'#'); // ignore until hash character
sstr >> b;
std::cout << "a: " << a << " b: " << b << std::endl;
}