STL map, using iterators and pointers of classes - c++

I am having trouble accessing data inside a map using an iterator. I want to return all of the values inserted into the map by using an iterator. However, when I use the iterator, it doesn't acknowledge any of the members in the instance of the class it has been past into.
int main()
{
ifstream inputFile;
int numberOfVertices;
char filename[30];
string tmp;
//the class the holds the graph
map<string, MapVertex*> mapGraph;
//input the filename
cout << "Input the filename of the graph: ";
cin >> filename;
inputFile.open(filename);
if (inputFile.good())
{
inputFile >> numberOfVertices;
inputFile.ignore();
for (int count = 0; count < numberOfVertices; count++)
{
getline(inputFile, tmp);
cout << "COUNT: " << count << " VALUE: " << tmp << endl;
MapVertex tmpVert;
tmpVert.setText(tmp);
mapGraph[tmp]=&tmpVert;
}
string a;
string connectTo[2];
while (!inputFile.eof())
{
//connectTo[0] and connectTo[1] are two strings that are behaving as keys
MapVertex* pointTo;
pointTo = mapGraph[connectTo[0]];
pointTo->addNeighbor(mapGraph[connectTo[1]]);
//map.find(connectTo[0]).addNeighbor(map.find(connectTo[1]));
//cout << connectTo[0] << "," << connectTo[1] << endl;
}
map<string,MapVertex*>::iterator it;
for (it=mapGraph.begin(); it!=mapGraph.end(); it++)
{
cout << it->getText() << endl;
}
}
return 0;
}
Compiler output:
\lab12\main.cpp||In function `int main()':|
\lab12\main.cpp|69|error: 'struct std::pair<const std::string, MapVertex*>'
has no member named 'getText'|
||=== Build finished: 1 errors, 0 warnings ===|
There is an access member in my MapVertex class called getText() which returns the data that is inside it.

To fix the compiler error, you need to do it->second->getText() as the *iterator is a pair<string, MapVertex*>. But there are other problems in your code. While inserting into the map, you are inserting the address of a local variable into it. This address will be invalid by the time you try to iterate the map using for loop. I would suggest you to declare the map as std::map<string, MyVertex> so that when you insert into the map a copy of the MyVertex is inserted into map.

tmpVert is the problem. Look, you create it on the stack. It is destroyed at the end of each for loop.
It Is Destroyed.
So, your mapGraph is holding pointers to objects that don't exist.

'struct std::pair' has no member named 'getText'
means that what the iterator returns is std::pair, not your object directly; the first element of the pair is the key, the second the value, so you need to get the value, and then call the method: it->second->method().

Related

How to loop through numbers stored in separte variables?

So I got this code, I input three numbers. Now I want to display them in a for loop. From smallest to biggest, and in another loop from biggest to smallest. How can I do this?
int main()
{
int num1, num2, num3;
cout << "Enter first num" << endl;
cin >> num1;
cout << "Enter second num" << endl;
cin >> num2;
cout << "Enter third num" << endl;
cin >> num3;
}
I have done this like this but I think it's not a proper way to do it.
for(int i = 0; i <= 0; i++) {
cout << num1;
cout << num2;
cout << num3;
}
for(int i = 0; i <= 0; i++) {
cout << num3;
cout << num2;
cout << num1;
}
Edit:
is this better?
int main()
{
int numbers[3];
cout << "Enter first num" << endl;
cin >> numbers[0];
cout << "Enter second num" << endl;
cin >> numbers[1];
cout << "Enter third num" << endl;
cin >> numbers[2];
for (int i = 0; i <= 2; i++)
{
cout << numbers[i];
}
for (int i = 2; i >= 0; i--)
{
cout << numbers[i];
}
return 0;
}
What you are trying to do is very common in programming languages. I.e. enter data, process it and output it in one form or another.
A loop is what you use if you want to execute a piece of code a number of times... a number more than 1 that is. In your first example you execute the loop once, making the extra lines of code a bit redundant.
You already updated your code, showing you quickly realized how to used arrays. These types of arrays (i.e. int numbers[3];) are often referred to as C-style arrays, as they were inherited from C. In C++ we can now use std::array<int, 3> numbers;, which more adheres to the C++ style of working. (The std:: part of the type name indicates the type is define in the standard library namespace. The type is part of the C++ language standard.)
The problem with these two types is that they have a static(=fixed) size. I.e. the size has to be know at compile time. This can be quite a limitation if you don't know how many items the user wants to enter. However, the C++ standard defines other container types which can hold a variable amount of items. Of these, std::vector is the dynamic (variable) size counterpart of the array: these both store their items sequentially in memory.
So you can for instance use a loop to add (to the back = push_back()) a number of elements selected by the user to the vector.
#include <vector>
#include <iostream>
[...]
std::vector<int> numbers;
std::cout << "How many numbers do you want to enter?\n";
int N;
std::cin >> N;
numbers.reserve(N); // good practice to reserve memory if you know the number of elements
for (int i = 0; i < N; ++i) {
std::cout << "Enter a number: ";
int number;
std::cin >> number;
numbers.push_back(number);
}
[...]
Note that there is no check on the input: e.g. if the user would enter "-1" after the first question, things would break. I will not consider handling user error in my answer.
You can already see some code duplication in here: cout, type definition, cin. You can extract this in a separate function.
#include <string>
[...]
int ReadUserInput(std::string const& message) {
std::cout << message;
int value;
std::cin >> value;
return value;
}
or even better, you make a function template. I.e. a template for a function: the compiler will generate implementations of this function for you, depending on the type T inferred. I also use the std::string_view now, which can view to different types of strings (std::string, char*)
#include <string_view>
[...]
template<typename T>
T ReadUserInput(std::string_view message = "") {
if (!message.empty()) std::cout << message; //only print is there's an actual message
T value;
std::cin >> value;
return value;
}
Next, the C++ library has more to offer, including a number of algorithms that are commonly used in programming. One of these is a generator that repeatedly calls a function, of which the result is used to assign successive elements in a container. The object that points to a specific element in the container is called an iterator. The C++ standard library offers a convenient iterator type that executes a push_back: the std::back_inserter. The previous code can now be reduced to:
int const N = ReadUserInput<int>("How many numbers do you want to enter?\n");
std::vector<int> numbers;
numbers.reserve(N); // good practice to reserve memory if you know the number of elements
std::generate_n(back_inserter(numbers), N, ReadUserInputGenerator<int>("Enter a number: "));
"But wait", you might ask, "what is this ReadUserInputGenerator?". Well, to make the generate_n work, you need to pass a pointer/handle to a generator function, which is then executed for each element. If we'd just call ReadUserInput<int>("Enter a number: "), then the function would already have been evaluated. So we need to add another intermediate function object that makes this generator function. In the passed we would make a class for this
template<typename T>
class ReadUserInputGenerator {
private:
std::string_view message;
public:
ReadUserInputGenerator(std::string_view message = "") : message(message) {}
T operator()() const { return ReadUserInput(message); }
};
... but now we can do using lambda expressions
template<typename T>
auto ReadUserInputGenerator = [](std::string_view message = "") {
return [message]() { return ReadUserInput<T>(message); };
};
(note to more experienced readers: I'm not really sure about passing string_view by value. I based it on this)
SOOOOO, now we finally have our input. Next you wanted to sort it. Again, this is a common operation. There are actually many ways to sort a collection a values and it is a good excersize to implement these yourself... However, like I mentioned previously, as these kind of operations are common in programming, the C++ standard library actually offers an algorithm for sorting: std::sort.
std::sort(begin(numbers), end(numbers));
^ here begin and end refer to iterators pointing to the begin and end (or actually one past the end) of your vector. You could sort only part of your vector this way. However, the most common case is just begin to end, so in C++20 they've introduced the ranges algorithms, and the previous statement can be reduced to
std::ranges::sort(numbers);
... AAAND now its sorted... printing is next. You can print using a loop... but even there you will have a number of choices. And indexed loop:
for (int i = 0; i < numbers.size(); ++i) {
std::cout << numbers[i] << ' ';
}
An iterator based for loop:
for (auto it = cbegin(numbers); it != cend(numbers); ++it) {
std::cout << *it << ' ';
}
Note: the 'c' before begin and end denote that it is a "const" qualified iterator, i.e. it may not modify the contents of the object it points to.
Or a range based for loop:
for (int number : numbers) {
std::cout << number << ' ';
}
There is also a special convenience iterator type that can push to the cout: std::ostream_iterator. You can copy the vector to this iterator using the algorithm std::copy
std::copy(cbegin(numbers), cend(numbers), std::ostream_iterator<int>(std::cout, " "));
Note, the second argument of the ostream_iterator is the delimiter, i.e. the string appended after each element. Of course there's also a C++20 ranges version.
std::ranges::copy(numbers, std::ostream_iterator<int>(std::cout, " "));
... FINALLY reversing.
One option is just to reverse all elements in the vector and print them out again using one of the above mentioned methods. There's of course an algorithm to do so: std::reverse.
std::reverse(begin(numbers), end(numbers));
However, this operation modifies the contents of the container(vector), which might be costly. If you don't want to to this, you'll have to loop though your vector in reverse order
for (int i = numbers.size() - 1; i >= 0; --i) {
std::cout << numbers[i] << ' ';
}
This looks complex, and it's easy to make errors.
You could instead use the reverse iterators of vector, to traverse through the vector in reverse order: (you need to add an 'r' to the begin/end)
for (auto it = crbegin(numbers); it != crend(numbers); ++it) {
std::cout << *it << ' ';
}
or
std::copy(crbegin(numbers), crend(numbers), std::ostream_iterator<int>(std::cout, " "));
For C++20, there's no range operation to reverse the vector. However, they introduced "views" that are used to observe the values in the vector in a specific way. One such a way is "observe it in reverse order": std::ranges::view::reverse. So in C++20 you will be able to do:
for (int number : numbers | std::views::reverse) {
std::cout << number << ' ';
}
or
std::ranges::copy(numbers | std::views::reverse, std::ostream_iterator<int>(std::cout, " "));
which both don't modify numbers.
The end code could look a little bit something like this (pre C++20 version):
#include <vector>
#include <iostream>
#include <string_view>
#include <iterator>
#include <algorithm>
template<typename T>
T ReadUserInput(std::string_view message = "") {
if (!message.empty()) std::cout << message; //only print is there's an actual message
T value;
std::cin >> value;
return value;
}
template<typename T>
auto ReadUserInputGenerator = [](std::string_view message = "") {
return [message]() { return ReadUserInput<T>(message); };
};
int main() {
int const N = ReadUserInput<int>("How many numbers do you want to enter?\n");
std::vector<int> numbers;
numbers.reserve(N); // good practice to reserve memory if you know the number of elements
std::generate_n(back_inserter(numbers), N, ReadUserInputGenerator<int>("Enter a number: "));
std::sort(begin(numbers), end(numbers));
std::copy(cbegin(numbers), cend(numbers), std::ostream_iterator<int>(std::cout, " "));
std::cout << '\n';
std::copy(crbegin(numbers), crend(numbers), std::ostream_iterator<int>(std::cout, " "));
std::cout << '\n';
}
Given that the input doesn't seem to have any meaning other than "some numbers", you should use a container, and the obvious choice would be:
std::vector<int> nums;
From c++20, you don't needs loops at all for this problem, because you can use ranges:
#include<ranges>
namespace rs = std::ranges;
namespace rv = std::views;
and now you can read in numbers like this:
rs::copy_n(std::istream_iterator<int>(std::cin), 3,
std::back_inserter(nums));
I'm not sure if you want to use the order that the user inputs the numbers, but if you want the actual smallest to largest, you can do:
rs::sort(nums);
Now to print out the numbers:
rs::copy(nums,
std::ostream_iterator<int>(std::cout, " "));
and in reverse:
rs::copy(nums | rv::reverse,
std::ostream_iterator<int>(std::cout, " "));
Here's a demo.
You don't need a loop, the one you have it's not really a loop in the sense that it only cycles once, you can use a chained ostream:
cout << num1 << " " << num2 << " " << num3 << "\n";
And
cout << num3 << " " << num2 << " " << num1 << "\n";
But if you want print them sorted by value and you can't use some container where you can apply a sorting algorithm, you'll need some conditionals.
EDIT: Is this better?
Storing it in an array makes it easier to deal with the data, for instance, it will allow you to sort it by value using something as simple as <algorithm> library std::sort.

My code seems to enter an infinite loop after loading a list from a binary file

I was studying the STL and decided to write some code to practice the writing and reading of files. The problem consists of creating a list of int (0, 1,...,9), save it in a binary file, and finally load it again.
There are 5 basic code blocks:
1. create list
2. present list
3. save list
4. load list
5. present list again
It seems simple and straightforward; however, the code seems to get in an infinite loop.
int main(){
list<int> numbers;
/////// Create list of 10 integers ///////
for(int i=0; i<10; i++){
numbers.push_back(i);
}
/////// Present List ///////
cout << "List created: [";
list<int>::iterator it;
for(it = numbers.begin(); it != numbers.end(); it++){
if(*it != 9){
cout << *it << ", ";
}
else{
cout << *it;
}
}
cout << "]" << endl;
/////// Save list ///////
string fileName = "test.bin";
ofstream outputFile;
outputFile.open(fileName, ios::binary);
if(outputFile.is_open()){
outputFile.write(reinterpret_cast<char *>(&numbers), sizeof(numbers));
outputFile.close();
cout << "List saved to file." << endl;
}
else{
cout << "Could not open file named " << fileName << endl;
}
/////// Load list ///////
list<int> anotherList;
ifstream inputFile;
inputFile.open(fileName, ios::binary);
if(inputFile.is_open()){
inputFile.read(reinterpret_cast<char *>(&anotherList), sizeof(anotherList));
inputFile.close();
cout << "List loaded from file." << endl;
}
else{
cout << "Could not open file named " << fileName << endl;
}
/////// Present List ///////
cout << "List loaded: [";
for(it = anotherList.begin(); it != anotherList.end(); it++){
if(*it != 9){
cout << *it << ", ";
}
else{
cout << *it;
}
}
cout << "]" << endl;
return 0;
}
The problem is in the "Load List" code block, since, if I comment it out, everything works fine.
Am I saving the object correctly? What am I doing wrong?
Thanks in advance.
The problem lies in the flawed logic of reinterpret_cast<char *>(&numbers). Why?
std::list manages its storage using pointers. It simply holds a pointer to a chain of elements consisting of some objects and a pointer to the next element. You cannot simply treat it like a sequence of bytes and expect it to maintain its functionality.
What you instead need to do is to loop over the elements and write them to the file one by one:
#include <fstream>
#include <iostream>
#include <list>
int main() {
std::fstream file{};
file.open("data.txt", std::ios::binary | std::ios::out);
std::list<int> ints{2, 4, 5, 6, 8, 1, 3, 5, 7, 9};
for (int i : ints) {
file.write(reinterpret_cast<char*>(&i), sizeof(i));
}
file.flush();
file.close();
file.open("data.txt", std::ios::binary | std::ios::in);
ints.clear();
std::cout << "Before reading the file, size of the list is: " << ints.size() << '\n';
for (int i; file.read(reinterpret_cast<char*>(&i), sizeof(i)); ints.push_back(i));
for (int i : ints) {
std::cout << i << ' ';
}
}
Clarification of the second for loop:
for (int i; file.read(reinterpret_cast<char*>(&i), sizeof(i)); ints.push_back(i));
We declare a variable i since we need a place where we read the data. This one should be quite clear. We do not need to initialize i, since the condition of the loop will take care of that (although it would probably be a good practice to do it anyway).
The condition part: file.read(reinterpret_cast<char*>(&i), sizeof(i)). This may seem tricky at first, but it really isn't! First of all, we have a method call. We call std::basic_istream::read, specifying the two arguments - first, the memory address where to read the variable, and second, the number of bytes we want to read. The trick is that the read method not only reads and saves the data - it also returns the stream, so essentially after the data processing, we are left with the condition file. But it's not a bool, is it? Correct, it's not a bool (neither an int), but stream objects can be implicitly converted to bool, which is exactly what happens here! The rules are as follows: if the stream is in a correct state, the conversion returns true. It returns false otherwise. An incorrect state may be cauased, for example, by failure to read, which happens, for example, when you already have read the whole file. Essentially, this part both reads from the file and checks whether the reading process executed successfully. It's both the reading logic and the condition!
The third part: ints.push_back(i). Notice that this part only executes if the condition (reading from the file) executed successfully. It simply adds the read int (i) to the ints container.
All in all, you can read the for loop in the following way:
create a variable i, which will store, one by one, the variables from the file
as long as reading from the file is successful...
...add the read value to the container
outputFile.write(reinterpret_cast<char *>(&numbers), sizeof(numbers));
What you actually print is the binary representation of the list object itself. Unfortunately, it does not contain the data of the list directly, but instead looks similar to something like this:
template <typename T>
class list
{
struct node
{
node* next;
node* previous;
T data;
};
node* m_head;
node* m_tail;
size_t m_size;
public:
// ...
};
No direct link to the data. Even worse: With std::list, the data can get shattered all over your memory (in contrast to std::vector which assures contiguous data).
So you only can iterate over your list again (either with the iterator variant you chose already before or, more convenient, with a range based for loop):
for(auto n : numbers)
{
outputFile.write(reinterpret_cast<char*>(&n), sizeof(n));
}
Reading is different; you don't know the size in advance, do you? Well, there are ways to retrieve it (seekg, tellg), but that's more of interest if you want to read all the data into contiguous memory at once (you could reserve sufficient of in a std::vector), but that's another issue.
For the list approach:
int n;
while(inputFile.read(reinterpret_cast<char*>(&n), sizeof(n)))
{
anotherList.push_back(n);
}

I can't create proper std::vector in c++

I have to create a list of objects, so to keep it as smooth as possible I wanted to use std::vector instead of regular array of objects. However I don't exactly know how to use them properly. Here is sample code from my class in which I try to add objects to vecor and in 2nd method to display them.
private:
vector <Student> stud;
public:
void Student::dodaj(){
stud.push_back(Student(getNaz(), getImie(), getLeg(), getRok()));
}
void Student::wypisz(){
cout << "Lista osob:\n";
for (int i = 0; i < 1; i++)
{
cout << endl;
cout << "Nazwisko: " << stud[i].nazwisko << endl;
cout << "Imie: " << stud[i].imie << endl;
cout << "Numer indeksu.: " << stud[i].legitymacja << endl;
cout << "Rok studiow: " << stud[i].rok << endl;
}
And my main:
Student st("Kowalski", "Jan", 123455, 2);
Student test1("Nowak", "Jan", 123, 2);
st.dodaj();
test1.dodaj();
test1.wypisz();
However the output result is only one object for wich I use wypisz(print/display) method, in this case test1, that is displayed instead of two of them - st and test1. Any ideas how to fix that?
It appears that stud is a member of Student, which means that each Student object contains its own vector. When you call dodaj on a particular student, it just adds its own details to the vector it contains.
st.dodaj(); // Adds details of st to st's own vector
test1.dodaj(); // Adds details of test1 to test1's own vector
// Now we have one vector inside st containing a single element
// and one vector inside test1 containing a single element
test1.wypisz(); // Prints elements from test1's vector
Instead, your vector should be outside of your Student class (or at least static). One option would be to do this:
Student st("Kowalski", "Jan", 123455, 2);
Student test1("Nowak", "Jan", 123, 2);
std::vector<Student> stud;
std.push_back(st);
std.push_back(test1);
You may want to have another class (maybe School?) which contains this vector.

is accessing two vectors in maps impossible?

Since I am beginner to c++, I was trying to play with . So, I succeeded in
map<string,int>
And,
map <string, vector<int> >
But, I was curious if having two vectors in a map can be accessible. So, what should I do?
int main()
{
map<vector<string>,vector<int> > a;
vector<int> okay;
vector<string> knot;
knot.push_back("name1"); //this for inserting in vector<string>
knot.push_back("name2");
okay.push_back(1); //this on for vector<int>
okay.push_back(2);
a[knot]=okay;
map<vector<string>,vector<int> >::iterator i=a.begin();
cout<<i->first<<endl; //error shows here, how am i accessing this?
++i;
cout<<i->first; //this too. Function resembles the same of above. So, ERROR!!
return 0;
}
This line:
cout<<i->first<<endl;
You are doing the right thing, but i->first returns a vector because you have a map of vectors. There is no overloaded << (print) operator for vector<string> (but you could make your own). But if you:
auto temp = i->first;
for(const auto &a : temp) {
cout << a << endl;
}
This will print all the elements stored in the vector in i->first.
If you wish the print the first element stored in the vector, you can do:
cout << *((i->first).begin()) << endl; //iterator method or
cout << (i->first)[0] << endl;
Do not attempt to print vector elements without checking emptiness.

how can I pass an array pointer to a function

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
typedef vector <double> record_t;
typedef vector <record_t> data_t;
int sorted(int *data,int max_record_size)
{
}
int main()
{
// Here is the data we want.
data_t data;
// Here is the file containing the data. Read it into data.
ifstream infile( "sort.txt" );
infile >> data;
// Complain if something went wrong.
if (!infile.eof())
{
cout << "Fooey!\n";
return 1;
}
infile.close();
// Otherwise, list some basic information about the file.
cout << "Your CSV file contains " << data.size() << " records.\n";
unsigned max_record_size = 0;
for (unsigned n = 0; n < data.size(); n++)
if (max_record_size < data[ n ].size())
max_record_size = data[ n ].size();
cout << "The largest record has " << max_record_size << " fields.\n";
int i;
for (i=0; i <= max_record_size; i++)
{
cout << "your data contains " << data[ 0 ][ i ] << ".\n";
int temp[max_record_size];
sorted(&data,max_record_size);
//cout << "Your sorted data contains" << sorted [0] [i] << ".\n";
}
cout << "Good bye!\n";
system("PAUSE");
return 0;
}
cannot convert data_t*' toint*' for argument 1' toint
sorted(int*, int)'
Im trying to pass the pointer of my data which should be an array containing my list of numbers to my sort function what exactly am I doing wrong and can you please explain in detail so I can understand it, thanks!
You don't have an array. An array in C (or C++) would be just a list of integers, and you could pass it like you did.
However, you have a vector (and I'm guessing record_t ends up being an int). vector<>s behave a lot like arrays, but they are not, they are actual objects.
What you probably want to do is write your function as
int sorted(data_t& data, int max_record_size)
and your call simply as
sorted(data,max_record_size);
data_t is a vector<vector<double>>. This is not even close to an array of ints. You just need to write a function that handles data_t and not ints.
If the function is supposed to sort data then you should use std::sort, and to do that you'll need to write a comparison function that can compare two elements of data in order to see which one should come before the other in the sorted result.
Here's an example of providing such a comparison function using a lambda with lexicographical_compare.
sort(begin(data),end(data), [](record_t const &lhs,record_t const &rhs) {
return lexicographical_compare(begin(lhs),end(lhs),begin(rhs),end(rhs));]
});
Try passing a reference to the original vector. Why?
You still want to work with the original vector without making a copy
You can't pass a null reference, so the argument is always valid
You're not doing any pointer arithmetic, so be safe and use a reference
int sorted(data_t& data, int max_record_size)
{
}
Pass your data_t structure like this:
sorted(data, max_record_size);
Now you have access to the data_t structure inside your sorted function.
Also
You know that your code is not going to compile?
unsigned max_record_size and int temp[max_record_size] are invalid. If you're going to allocate an array on the stack, you need to use a constant size.
There is no overload on the >> operator in the istream class that handles vector<vector<double>>, so this statement is broken as well: infile >> data;