Problems printing from map<string, struct> in C++ - c++

I'm learnig C++. Here is my problem: I'm trying to read data from a text file and save it to a map<string, struct> and then have it print out all the keys from the map preferably in alphabetical order. The data has 2 strigns and a float. I can't get this to print even after having tried many different solutions.
Heres what I've got so far:
Here is my struct:
struct category
{
std::string tram_stop;
float dist;
};
using Tram = std::map<std::string, std::vector<category>>;
Here is where I try to save the data to the map.
void store(Tram& tram, std::vector<std::string>& tram_data)
{
if (tram.find (tram_data.at (0)) == tram.end ())
{
tram[tram_data.at (0)] = {};
}
else
{
tram.at (tram_data.at (0)).push_back (category {tram_data.at (1), std::stof(tram_data.at(2))});
}
}
And here is main().
int main()
{
Tram tram;
print_rasse();
// Ask input filename.
std::string filename;
std::cout << "Give a name for input file: ";
std::cin >> filename;
// Read input file.
std::ifstream file_in;
file_in.open (filename);
if (!file_in.is_open ())
{
std::cout << INVALID_FILE << std::endl;
return EXIT_FAILURE;
}
std::vector<std::string> tram_data;
if (file_in.is_open())
{
std::string line;
while( std::getline(file_in,line) )
{
std::stringstream ss(line);
std::string tram_line, tram_stop, distance;
std::getline(ss,tram_line,';'); //std::cout<< ""<<tram_line <<" ";
std::getline(ss,tram_stop,';'); //std::cout<<" "<<tram_stop<<" ";
std::getline(ss,distance); //std::cout<<" "<<distance<< " ";
if (tram_line != "" && tram_stop != "")
{
tram_data.push_back (tram_line);
tram_data.push_back (tram_stop);
tram_data.push_back (distance);
//std::cout << tram_line << " " << distance << std::endl;
}
else
{
std::cout << INVALID_FORMAT << std::endl;
return EXIT_FAILURE;
}
}
file_in.close ();
store(tram, tram_data);
}
This is the part I think doesn't work. Tried different iterators too.
if (upper_com == "LINES")
{
std::cout << "All tramlines in alphabetical order:" << std::endl;
for (auto& item : tram)
{
std::cout << item.first << std::endl;
}
}

Your implementation of store will create a vector for the first item added for a particular tram_data[0] value, but will not add anything to the vector. This results in losing that first item, and can result in no output because of the empty vectors.
That function can be simplified:
void store(Tram& tram, std::vector<std::string>& tram_data)
{
if (tram_data.size() < 3) throw std::out_of_range();
tram[tram_data[0]].emplace_back(tram_data[1], std::stof(tram_data[2]));
}
You don't need to use at with tram because you want to create the entry if it doesn't exist. at with tram_data will result in an exception being thrown if there are fewer than three elements in tram_data, so that check has been moved outside all the accesses to the vector.

Related

Operating through data container map<string,map<string,int>>

I need to find the fastest way from one city to other using djikstra algorithm (working on directed graph with weights), but i have to use this specific container. I have a problem with saving data properly to this map, here is part of my code.
fstream file;
string fromThisCity, toThisCity;
int distance;
file.open("drogi.txt", ios::in);
if (file.good())
{
while (!file.eof())
{
file >> fromThisCity;
file >> toThisCity;
file >> distance;
map<string, int> inGraph;
inGraph[toThisCity] = distance;
graph[fromThisCity] = inGraph;
for (map<string, map<string, int>>::iterator element = graph.begin(); element != graph.end(); element++)
{
for (map<string, int>::iterator i = inGraph.begin(); i != inGraph.end(); i++)
{
cout << element->first << " " << i -> first << " " << i -> second << endl;
}
}
}
}
else
{
cout << "file couldnt be opened" << endl;
}
file.close();
Every time you read a fromThisCity, you set that key's value in graph to a new single-value map<string,int>, discarding any mapping you already had.
You want to modify the map graph[fromThisCity], not replace it, i.e. graph[fromThisCity][toThisCity] = distance;.
Fixing some other problems, you might end up with
ifstream file("drogi.txt"); // Use ifstream for reading, open on initialization.
if (file.good())
{
// Declare variables as near to use as possible.
string fromThisCity, toThisCity;
int distance;
// This is the "standard" and least surprising read loop.
while (file >> fromThisCity >> toThisCity >> distance)
{
// Update the graph.
graph[fromThisCity][toThisCity] = distance;
// Since you're apparently in C++17, use modern loops.
for (const auto& from: graph)
{
for (const auto& to: from.second)
{
cout << from.first << " " << to.first << " " << to.second << endl;
}
}
}
}
else
{
cout << "File couldn't be opened" << endl;
}
// You don't need to explicitly close fstreams; the destructor does it if appropriate.
Having this kind of map is wasteful.
Note that for the problem itself you do not need to have city names.
Also learn to split code into small functions to make it easier to maintain.
Take a look on that:
struct Data {
std::map<std::string, int> namesToIds;
std::vector<std::string> names;
std::vector<std::vector<int>> distances;
int addCity(const std::string& city) {
auto cityIdCandidate = static_cast<int>(namesToIds.size());
auto [it, newInsert] =
namesToIds.insert(std::pair{city, cityIdCandidate});
if (newInsert) {
names.push_back(city);
updateDistancesSize();
}
assert(names.size() == namesToIds.size());
return it->second;
}
void addRoute(int src, int dst, int dist) {
distances[src][dst] = dist;
}
void addRoute(const std::string& src, const std::string& dst, int dist) {
addRoute(addCity(src), addCity(dst), dist);
}
void updateDistancesSize() {
auto newSize = names.size();
distances.resize(newSize, std::vector(newSize, -1));
for(auto& row : distances) {
row.resize(newSize, -1);
}
}
};
std::istream& operator>>(std::istream& in, Data& data) {
std::string src, dst;
int dist;
while (in >> src >> dst >> dist) {
data.addRoute(src, dst, dist);
}
return in;
}
void loadData(std::filesystem::path name, Data& data) {
std::ifstream stream{name};
stream >> data;
}
https://godbolt.org/z/q194a9dG6
Concerns are spited, use of file is not obligatory, city names are kept separately from adjacency matrix. Code is easy to read and maintain.

Trying to code Graph in c++, getting bad_alloc some of the time

I'm new to c++ after learning basic Object Oriented Programming in Java so I'm having a difficult time grasping memory deallocation. The assignment was to create a Weighted Directed Graph...
I'm getting the error: "terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc" when I run certain inputs through my code, and I'm having a difficult time figuring out what is causing it.
I googled the error and found that it was a memory problem, so I attempted to go through my code and try to find any leaks, but I am not sure where they are. Most posts are talking about pointers, which I do not tend to implement because I am unfamiliar with them. Thank you for your time!
#include <iostream>
#include <fstream>
#include <string>
#include <array>
#include <iterator>
#include <map>
#include <list>
#include <vector>
#include <algorithm>
using namespace std;
class WDGraph {
private:
map<string,map<string,int>> edges;
vector<string> verts;
list<string> leaves;
list<string> roots;
list<string> selfEdges;
public:
list<string> getRoots() { return roots; }
list<string> getLeaves() { return leaves; }
void addVert(string key) {
verts.push_back(key);
}
void link(string start, string dest, int cost) {
edges[start].insert(make_pair(dest,cost));
if (!containsLeaf(dest) && !containsVert(dest))
leaves.push_back(dest);
if (!containsRoot(start) && !containsVert(start))
roots.push_back(start);
if (start == dest)
selfEdges.push_back(start);
roots.remove(dest);
leaves.remove(start);
}
bool containsVert(string key) {
for (int i=0; i < verts.size(); i++) {
if (key == verts[i]) {
return true;
}
}
return false;
}
bool containsRoot(string key) {
bool found = (find(roots.begin(), roots.end(), key) != roots.end());
return found;
}
bool containsLeaf(string key) {
bool found = (find(leaves.begin(), leaves.end(), key) != leaves.end());
return found;
}
WDGraph() { }
void printWDG() {
cout << "Printing Weighted Directed Graph." << endl;
for (auto itr1 = edges.begin(); itr1 != edges.end(); ++itr1) {
for (auto itr2 = itr1->second.begin(); itr2 != itr1->second.end(); ++itr2) {
if (itr2->first == "null" && containsRoot(itr1->first)) {
cout << "[" << itr1->first << "]";
}
else if (itr2->first != "null")
cout << "[" << itr1->first << " -> ";
cout << itr2->first << ", " << itr2->second << "] ";
}
cout << "" << endl;
}
}
void printNumVerts() {
cout << "Total number of vertices: " << verts.size() << endl;
}
void printRoots() {
int num_roots = 0;
cout << "Vertices with zero inbound edges: " << endl;
for (auto itr = roots.begin(); itr != roots.end(); ++itr) {
cout << "[" << *itr << "]" << endl;
num_roots++;
}
if (num_roots == 0) cout << "None" << endl;
}
void printLeaves() {
int num_leaves = 0;
cout << "Vertices with zero outbound edges:" << endl;
for (auto itr = leaves.begin(); itr != leaves.end(); ++itr) {
if (*itr != "null")
cout << "[" << *itr << "]" << endl;
num_leaves++;
}
if (num_leaves == 0) cout << "None" << endl;
}
void printSelfEdges() {
cout << "Vertices with self edges:" << endl;
for (auto itr = selfEdges.begin(); itr != selfEdges.end(); ++itr) {
cout << "[" << *itr << "]" << endl;
}
}
};
int main() {
WDGraph myWDG;
string filePath;
string line;
int weight;
size_t commaPos;
vector<string> sVector;
ifstream dataFile;
// cout << "Please enter the relative path to an input file." << endl;
// getline (cin, filePath);
// cout << "The file path you entered was " << filePath << endl;
// dataFile.open(filePath);
dataFile.open("input.csv"); //test input
while (getline (dataFile, line)) {
commaPos = line.find(',');
//Parse input file into string vector
while (line.length() >= 1) {
if (line.length() == 1) {
sVector.push_back(line);
break;
}
sVector.push_back(line.substr(0,commaPos));
line = line.substr(commaPos+1);
commaPos = line.find(',');
}
//Create vertices depending on number of parameters
if (sVector.size() == 1) {
if (!myWDG.containsVert(sVector[0])) {
myWDG.addVert(sVector[0]);\
}
myWDG.link(sVector[0], "null", 0);
}
if (sVector.size() == 3) {
if (!myWDG.containsVert(sVector[0])) {
myWDG.addVert(sVector[0]);
}
if (!myWDG.containsVert(sVector[1])) {
myWDG.addVert(sVector[1]);
}
weight = stoi(sVector[2]);
myWDG.link(sVector[0], sVector[1], weight);
}
sVector.clear();
}
myWDG.printWDG();
myWDG.printNumVerts();
myWDG.printRoots();
myWDG.printLeaves();
myWDG.printSelfEdges();
}
When my .csv has simple stuff it works as expected, for example:
a,b,1
c,d,2
e
f,f,3
However, if I have stuff like this I get the error "terminate called after throwing an instance of 'std::bad_alloc':
Hello
World,Hello,3
My,Name,4
Is
Nikki,Hello,3
As mentioned by Z E Nir, your line parsing code fails to consume any input if there is no comma "," in the line. You can of course debug your line parsing code, as debugging is a valuable skill to develop anyway.
However, a possible alternative to debugging consists in finding an existing C++ language construct that does what you want to do, and is part of the language library so it is already debugged.
Quite often, what you want to do is "common stuff", so debugging manual code will take more time than finding the appropriate pre-existing language construct, courtesy of your favorite internet search engine and/or stackoverflow itself. And being able to quickly find the language construct is also a very valuable skill.
In your case, function getline() takes an optional delimiter, which is a newline by default, but you can instead have "," as delimiter and so use getline() again, but to parse a single line. It just takes a string object pretending to be a file stream, that is an std::istringstream object.
So you end up with two nested loops, both using getline():
#include <sstream>
while (getline (dataFile, line)) {
std::istringstream iss{line};
std::string token;
while (getline (iss, token, ',')) {
std::cout << "DEBUG TOKEN LEN=" << token.length() << std::endl;
sVector.push_back(token);
}
// go build myWDG
}
That way, you don't have to mess up with lowly details such as the value of your commaPos variable. And the resulting code is easier to understand for another programmer.
Welcome to Stack Overflow.
Heads up: Sorry for the style, but you really have to learn solving those kind of problem on your own. It's called debugging. I'm experienced programmer and yet, my code never run exactly as I thought it will when testing it in the first time. You need to learn how to use a debugger like gdb or the built in debugger in the Visual C++ environment.
Now about your question:
The following code received the variable line with value Hello. There is no , character in line hence line = line.substr(commaPos + 1); return Hello all the time, and since 'Hello' string holds more then one character, you stuck in an infinte loop.
//Parse input file into string vector
while (line.length() >= 1) {
if (line.length() == 1) {
sVector.push_back(line);
break;
}
sVector.push_back(line.substr(0, commaPos));
line = line.substr(commaPos + 1);
commaPos = line.find(',');
}
The problem isn't stop there. Since each iteration over the infinite loop your program executing: sVector.push_back(line.substr(0, commaPos)); you actually allocates more and more memory, till you system won't give any more to this process. That's where you get the bad_alloc exception.
In other words, your error is not about C++, but about poor programing.
Reconsider your program, and think how you want to handle edge-cases like the Hello.
Oh, and never build objects on the stack. I know some places claim its OK to do this in the main function, but belive me its causing a lot of troubles.

Assertion failure when writing to a text file using ofstream

I am trying to write some string data to a .txt file that i read from the user but after doing so, the program shuts down instead of continuing and when i check the results inside the .txt file i see some part of the data and then some gibberish, followed by an assertion failure error! Here's the code:
#include "std_lib_facilities.h"
#include <fstream>
using namespace std;
using std::ofstream;
void beginProcess();
string promptForInput();
void writeDataToFile(vector<string>);
string fileName = "links.txt";
ofstream ofs(fileName.c_str(),std::ofstream::out);
int main() {
// ofs.open(fileName.c_str(),std::ofstream::out | std::ofstream::app);
beginProcess();
return 0;
}
void beginProcess() {
vector<string> links;
string result = promptForInput();
while(result == "Y") {
for(int i=0;i <= 5;i++) {
string link = "";
cout << "Paste the link skill #" << i+1 << " below: " << '\n';
cin >> link;
links.push_back(link);
}
writeDataToFile(links);
links.clear(); // erases all of the vector's elements, leaving it with a size of 0
result = promptForInput();
}
std::cout << "Thanks for using the program!" << '\n';
}
string promptForInput() {
string input = "";
std::cout << "Would you like to start/continue the process(Y/N)?" << '\n';
std::cin >> input;
return input;
}
void writeDataToFile(vector<string> links) {
if(!ofs) {
error("Error writing to file!");
} else {
ofs << "new ArrayList<>(Arrays.AsList(" << links[0] << ',' << links[1] << ',' << links[2] << ',' << links[3] << ',' << links[4] << ',' << links[5] << ',' << links[6] << ',' << "));\n";
}
}
The problem lies probably somewhere in the ofstream writing procedure but i can't figure it out. Any ideas?
You seem to be filling a vector of 6 elemenents, with indices 0-5, however in your writeDataToFile function are dereferencing links[6] which is out of bounds of your original vector.
Another thing which is unrelated to your problem, but is good practice:
void writeDataToFile(vector<string> links)
is declaring a function which performs a copy of your vector. Unless you want to specifically copy your input vector, you most probably want to pass a const reference, like tso:
void writeDataToFile(const vector<string>& links)

C++ - Reading Columns of a CSV files and only keeping ones that start with a specific string

so I am trying to figure out how to sort CSV files to help organize data that I need for an economics paper. The files are massive and there are a lot of them (about 587 mb of zipper files). The files are organized by columns in that all the variable names are in the first line and all the data for that variable is all below it. My goal is to be able to only take the columns that start with the an indicated string (ex input: "MC1", Get: MC10RT2,MC1WE02,...) and then save them into a separate file. Does anyone have any advice as to what the form that the code should take?
Just for fun a small program that should work for you. The thing you'll be intersted in is boost::split(columns, str, boost::is_any_of(","), boost::token_compress_off); that here create a vector of string from your csv-style string.
Very basic example, but your question was an excuse to play a bit with boost string algorithms, that I did know but never used...
#include <boost/algorithm/string.hpp>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <set>
// Typedefs for eye candy
typedef std::vector<std::string> Fields;
typedef std::vector<Fields> Results;
typedef std::set<unsigned long> Columns;
// Split the CSV string to a vector of string
Fields split_to_fields(const std::string& str)
{
Fields columns;
boost::split(columns, str, boost::is_any_of(","),
boost::token_compress_off);
return columns;
}
// Read all the wanted columns
Results read_columns_of_csv(std::istream& stream, const Columns& wanted_columns)
{
std::string str;
Results results;
while (getline(stream, str))
{
Fields line{split_to_fields(str)};
Fields fields;
for (unsigned long wanted_column: wanted_columns)
{
if (line.size() < wanted_column)
{
std::cerr << "Line " << (results.size() + 1 )
<< " does not contain enough fields: "
<< line.size() << " < " << wanted_column
<< std::endl;
}
else
{
fields.push_back(line[wanted_column]);
}
}
results.push_back(fields);
}
return results;
}
// Read the ids of the columns you want to get
Columns read_wanted_columns(unsigned long max_id)
{
Columns wanted_columns;
unsigned long column;
do
{
std::cin >> column;
if ((column < max_id)
&& (column > 0))
{
wanted_columns.insert(column - 1);
}
}
while (column > 0);
return wanted_columns;
}
// Whole read process (header + columns)
Results read_csv(std::istream& stream)
{
std::string str;
if (!getline(stream, str))
{
std::cerr << "Empty file !" << std::endl;
return Results{};
}
// Get the column name
Fields columns{split_to_fields(str)};
// Output the column with id
unsigned long column_id = 1;
std::cout
<< "Select one of the column by entering its id (enter 0 to end): "
<< std::endl;
for (const std::string elem: columns)
{
std::cout << column_id++ << ": " << elem << std::endl;
};
// Read the choosen cols
return read_columns_of_csv(stream, read_wanted_columns(column_id));
}
int main(int argc, char* argv[])
{
// Manage errors for filename
if (argc < 2)
{
std::cerr << "Please specify a filename" << std::endl;
return -1;
}
std::ifstream file(argv[1]);
if (!file)
{
std::cerr << "Invalid filename: " << argv[1] << std::endl;
return -2;
}
// Process
Results results{read_csv(file)};
// Output
unsigned long line = 1;
std::cout << "Results: " << results.size() << " lines" << std::endl;
for (Fields fields: results)
{
std::cout << line++ << ": ";
std::copy(fields.begin(), fields.end(),
std::ostream_iterator<std::string>(std::cout, ","));
std::cout << std::endl;
}
return 0;
}
I suggest using a vector of structures.
The structure will allow each row to have a different type.
Your program would take on the following structure:
Read data into a the vector.
Extra necessary fields out of each structure in the vector and write
to new file.
Close all files.

Read data into double array with c++

here is my problem. i have some two dimensional data with changing dimensionality, that i want to read into an 2d-array of doubles. Furthermore, there are at some points not number in the file but "NaN"s, that i want to be replaced by a zero. I made my code working so far, but i only managed to read integers. Maybe you could help me out to read it as doubles?
Here is what i got so far:
void READER(char filepath [], int target [129][128])
{
//---------------------------- header double & int
int rowA = 0;
int colA = 0;
std::string line;
std::string x;
std::cout << "reading file: " << filepath << "\n";
std::cout << std::endl;
std::ifstream fileIN;
fileIN.open(filepath);
if (!fileIN.good())
std::cerr << "READING ERROR IN FILE: " << filepath << std::endl;
while (fileIN.good())
{
while (getline(fileIN, line))
{
std::istringstream streamA(line);
colA = 0;
while (streamA >> x)
{
boost::algorithm::replace_all(x, "NaN", "0");
boost::algorithm::replace_all(x, ",", ""); //. rein
// std::cout << string_to_int(x) << std::endl;
target [rowA][colA] = string_to_int(x);
colA++;
}
rowA++;
if(rowA%5 ==0)
{
std::cout << "*";
}
}
}
std::cout << " done." <<std::endl;
}
this writes the files into 'target'. The function string to int looks the following:
int string_to_int (const std::string& s)
{
std::istringstream i(s);
int x;
if(!(i >> x))
return 0;
return x;
}
here you find some example data:
"exactly, thats what i thought about doing with the line boost::algorithm::replace_all(x, ",", ""); by replacing , by ."
Use following function to convert to any type, say double :-
template <typename T>
T StringToNumber ( const std::string &Text )
{
std::istringstream ss(Text);
T result;
return ss >> result ? result : 0;
}
Call using :
boost::algorithm::replace_all(x, ",", "."); // Change , to .
std::cout << StringToNumber<double>(x) << std::endl;
Or
you can simply use boost::lexical_cast
std::cout<<boost::lexical_cast<double>( x )<<std::endl;
Make sure you have a double 2D array