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.
Related
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.
I have an std::vector and the function expects an std::istream:
callMe(std::istream& is)
What is the best way to do the conversion? Is there something more clever than?
std::stringstream sstr;
for(int i = 0; i < myVector.size(); ++i) {
sstr << myVector[i] << " ";
}
std::istringstream istr{sstr.str()};
callMe(istr);
EDIT: Thanks for the suggestions so far! Updated code:
std::stringstream sstr;
for(const float& val : myVector) {
sstr << val << " ";
}
callMe(sstr);
The issue is that std::istream is inherently character-based. If you want to keep using callMe(std::istream& is) as an interface, you are bound to convert every element of myVector to characters and back at some point. If you want to stick with this option, I personally find ostream_iterator an elegant solution:
copy(begin(data), end(data), std::ostream_iterator<float>(sstr));
Full example:
void callMeStream(std::istream &is)
{
float f1;
is >> f1;
std::cout << "Stream: " << f1 << std::endl;
}
// ...
std::vector<float> data = {3.5, 1.5, 2.5 /* ... */};
std::stringstream sstr;
copy(begin(data), end(data), std::ostream_iterator<float>(sstr));
callMeStream(sstr); // Output: "Stream: 3.51"
If you are willing to change the signature of callMe, this conversion can be avoided:
template <class T>
void callMeTemplate(T &is)
{
float f1;
is >> f1;
std::cout << "Template: " << f1 << std::endl;
}
#define NO_ELEMENT -1.0;
class data_wrapper
{
std::vector<float>::const_iterator current;
const std::vector<float>::const_iterator last;
public:
data_wrapper(const std::vector<float> &data) : current(begin(data)), last(end(data)) {}
data_wrapper &operator>>(float &value)
{
if (last == current)
{
value = NO_ELEMENT;
}
else
{
value = *current++;
}
return *this;
}
};
// ...
data_wrapper wrapper(data);
callMeTemplate(wrapper); // Output: "Template: 3.5"
In the second example, the float value never gets converted to a character sequence, and could accept both data_wrapper and std::istream types. Of course, if you are willing to change the signature of callMe entirely, you might as well change it to accept a begin/end iterator range or a vector directly.
I am reading a text file which contains integers separated by a new line. Like this.
5006179359870233335
13649319959095080120
17557656355642819359
15239379993672357891
3900144417965865322
12715826487550005702
From this file, I want to access each integer in a loop and compare it with another, in order to match those two. In function File_read() I can print the integers. But what I want is to get it integer by integer outside the function. For example in main method, if there is a integer called x, I want to check whether x equals one of the integers in my text file.
string File_read() {
std::ifstream my_file("H:\\Sanduni_projects\\testing\\test.txt",
std::ifstream::binary);
if (my_file) {
string line;
for (int i = 0; i < 25; i++){
getline(my_file, line);
//cout << line << endl;
return line;
}
if (my_file)
std::cout << "all characters read successfully."<<endl;
my_file.close();
}
return 0;
}
Never return unconditionally inside a loop.
You are returning unconditionally from inside the loop. This causes the caller to exit the loop and return from the function during the first iteration.
for (int i = 0; i < 25; i++){
getline(my_file, line);
return line; // <-- Return from function (rest of iterations unreachable). Bad.
}
No need to reinvent stuff
Use the standard library to read the numbers, e.g., into a container std::vector.
std::vector<unsigned long long> v{std::istream_iterator<unsigned long long>{my_file},
std::istream_iterator<unsigned long long>{}};
Notice the value type of unsigned long long that is needed to fit the large numbers (you're pushing ~64 bits here).
Find a match
Use, e.g., std::find to find a possible match among the parsed numbers.
auto key = 15239379993672357891ull;
if (auto it = std::find(std::begin(v), std::end(v), key); it != std::end(v)) {
std::cout << "Key found at line " << std::distance(std::begin(v), it) + 1 << std::endl;
}
Here, I'm using a C++1z if(init; condition) statement to limit the scope of the iterator it to inside the if statement. It's optional of course.
Live example
You are, currently, just returning the first number (as a std::string and not a number). If you remove the return statement in your loop you can, of course, print each of them. Here is a slightly modified version of your File_read function that will return a std::vector<unsigned long long> that contains all the numbers. Then you can use this vector in, e.g., your main function to do your processing.
std::vector<unsigned long long> File_read()
{
std::vector<unsigned long long> numbers;
std::ifstream my_file("H:\\Sanduni_projects\\testing\\test.txt"); // Text files are not 'binany', i.e., removed std::ifstream::binary
if (my_file)
{
std::string line;
for (int i = 0; i < 25; i++)
{
std::getline(my_file, line);
numbers.push_back(std::stoull(line));
}
if (my_file)
{
std::cout << "all characters read successfully." << std::endl;
}
// my_file.close(); // Do not do this manually
}
return numbers;
}
Usage example:
int main()
{
unsigned long long x = /* some number */;
// Read all the numbers
std::vector<unsigned long long> vl = File_read();
// Run through all the numbers
for (unsigned long long y : vl)
{
// Check if any of the numbers are equal to x
if (x == y)
{
// There is a match...
// Do stuff
}
}
}
Update
The numbers cannot be held by in a long, however unsigned long long is sufficient.
std::vector<long> File_read(){
vector<long> numbers;
ifstream my_file("H:\\Sanduni_projects\\testing\\test.txt",
std::ifstream::binary);
if (my_file) {
string line;
for (int i = 0; i < frames_sec; i++){
getline(my_file, line);
numbers.push_back(std::stol(line));
}
if (my_file)
std::cout << "all characters read successfully." << endl;
else
std::cout << "error: only " << my_file.gcount() << " could be read" << endl;
my_file.close();
}
else{
cout << "File can not be opened" << endl;
}
return numbers;
}
Although the someone gives the answers that works correctly, I want to share my code.
#include <memory>
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
#define MAX_SIZE 4096
class FileRead
{
public:
FileRead(string path) :_file(path)
{
Reset();
}
void Reset()
{
memset(_buff, 0, MAX_SIZE);
}
string ReadLine()
{
if (!_file.is_open())
{
cout << "error open file" << endl;
return "";
}
if (!_file.eof())
{
Reset();
_file.getline(_buff,MAX_SIZE);
return string(_buff);
}
else
{
cout << "read file finished." << endl;
return "";
}
}
private:
ifstream _file;
string _line;
char _buff[MAX_SIZE];
};
int _tmain(int argc, _TCHAR* argv[])
{
FileRead fr("H:\\Sanduni_projects\\testing\\test.txt");
string line;
while (!(line = fr.ReadLine()).empty())
{
//do some compare..
}
return 0;
}
The other answers are correct about how return works, but there is something that acts how you thought return acted.
using string_coro = boost::coroutines::asymmetric_coroutine<std::string>
void File_read(string_coro::push_type & yield) {
std::ifstream my_file("H:\\Sanduni_projects\\testing\\test.txt", std::ifstream::binary);
if (my_file) {
string line;
for (int i = 0; i < 25; i++){
getline(my_file, line);
yield (line);
}
if (my_file)
std::cout << "all characters read successfully." << std::endl;
my_file.close();
}
}
Which is used like this
string_coro::pull_type(File_read) strings;
for (const std::string & s : strings)
std::cout << s << endl;
Ok I am having trouble printing the values in my list of structs. The normal way of printing a list (with the iterator) is not working. This is my code of me reading in a file into my list.
struct Edge{
map<char, string> edge;
int weight= -1;
Edge(){};
Edge(char v, string e, int w){
edge.insert(pair<char,string>(v,e));
weight = w;
}
};
int main(){
list<Edge> edges;
//Read in file//
string line;
char start;
vector<string> tokens;
if(!file.is_open()){
cout<<"Could not open file\n";
}else{
while(getline(file,line)){
tokens.clear();
start = line.front();
istringstream iss(line);
copy(istream_iterator<string>(iss),
istream_iterator<string>(),
back_inserter<vector<string>>(tokens));
for(int i = 1; i<tokens.size(); i+=2){
//cout << "Position: " <<i << " = " << tokens.at(i) <<endl;
//insert into edges list.
Edge e = Edge(start, tokens.at(i), stoi(tokens.at(i+1)));
edges.push_back(e);
}//end for
}//end while
}//end if-else
return 0;
}//end main
The vector tokens is read in properly. I checked it with the commented out cout.
The file is a graph file with the first element the start vertex and the rest of the line is formatted with the end vertex of the edge and the weight of that edge.
For example:
1 2 3 4 5
Would mean edge (1,2) has a weight of 3 and edge (1,4) has a weight of 5.
I don't know if I read in my list properly because I can't figure out how to print it out. How would I print out my list edges ?
Or is there a better way to set up my struct? Perhaps another struct for the edge and then a struct with the edge and weight?
Printing tried that won't work. The syntax doesn't even work. :(
The regular way to print a list. But doesn't like it since I have a struct list.
list<Edge>::iterator it;
for(it=edges.begin(); it!=edges.end(); it++){
cout << *it <<endl;
}//end for
And this was what I found when I searched. This is what I found.
C++ How to loop through a list of structs and access their properties
This is my attempt.
//inside main
list<Edge>::iterator it;
for(int i = 0; i<edges.size(); i++){
for_each(edges.begin(), edges.end(), printEdges);
}//end for
//outside main
void printEdges(Edge &data){
cout << "( " << data.edge << " )"
<< ": Weight = " << data.weight <<endl;
}//end printEdges
Thank you.
Update to comment:
friend std::ostream& operator<<(std::ostream& os, Edge const& edge)
{
os << "Edge { weight:" << edge.weight << " {\n";
for(edge_map::const_iterator it=edge.edge.begin(); it!=edge.edge.end(); ++it)
{
os << "{ '" << it->first << "', \"" << it->second << "\" } ";
}
return os << "\n}";
}
Should get you started.
it's int main, not void main
it's weight = w;, not weight = w.
You need to increment the loop variable. i+2 has no effect. Did you mean i+=2?
If you did, the loop condition should be fixed to avoid out-of-bounds addressing:
for(size_t i = 1; i+1<tokens.size(); i+=2){
note the use of size_t to avoid mixed signed/unsigned comparison
the Edge constructor should initialize weight
If you need //crutch comments, your code should be more legible :_)
Consider using a parser to read your input. You severely lack input validation, format flexibility and error handling.
Oh, don't use using namespace std
Here's an attempt at fixing some of the issues:
#include <string>
#include <map>
#include <list>
#include <vector>
#include <iostream>
#include <iterator>
#include <sstream>
#include <fstream>
#include <algorithm>
struct Edge{
typedef std::map<char, std::string> edge_map;
edge_map edge;
int weight;
Edge() : edge(), weight(0) {};
Edge(char v, std::string e, int w) : edge(), weight(w)
{
edge.insert(std::pair<char,std::string>(v,e));
weight = w;
}
friend std::ostream& operator<<(std::ostream& os, Edge const& edge)
{
os << "Edge { weight:" << edge.weight << " {\n";
for(edge_map::const_iterator it=edge.edge.begin(); it!=edge.edge.end(); ++it)
{
os << "{ '" << it->first << "', \"" << it->second << "\" } ";
}
return os << "\n}";
}
};
int main()
{
std::list<Edge> edges;
std::string line;
std::vector<std::string> tokens;
std::ifstream file("input.txt");
if(!file.is_open()){
std::cout<<"Could not open file\n";
}else{
while(std::getline(file,line)){
tokens.clear();
char start = line.front();
std::istringstream iss(line);
std::copy(std::istream_iterator<std::string>(iss),
std::istream_iterator<std::string>(),
std::back_inserter<std::vector<std::string>>(tokens));
for(size_t i = 1; i+1<tokens.size(); i+=2){
Edge e = Edge(start, tokens.at(i), stoi(tokens.at(i+1)));
edges.push_back(e);
}
}
}
}
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