简体   繁体   中英

How can I enter values into a map with a struct as key?

I have a map with a struct as key, I have overloaded the < operator, but the map is storing each entry as separate keys even though they are same. The code is as follows:

#include <iostream>
#include <vector>
#include <map>

using namespace std;

struct vertex 
{
    int color;
    vertex *pi;
    int index;

    bool operator<(const vertex & v ) const {
        return this->index < v.index;
    }
    bool operator==(const vertex & v) const {
        return this->index == v.index;
    }
};

int main()
{
    int x, y, num_edges;
    vector<vertex* > v;
    vertex *temp1, *temp2, *temp;
    map<vertex*, vector<vertex*> > m;
    map<vertex*, vector<vertex*> >::iterator it;

    cout << "\nEnter no. of edges: ";
    cin >> num_edges;

    for( int i = 0; i < num_edges; i++ )
    {
        cout << "\nEnter source: ";
        cin >> x;
        cout << "\nEnter dest: ";
        cin >> y;

        temp1 = new vertex;
        temp2 = new vertex;
        temp1->index = x;
        temp2->index = y;

        m[temp1].push_back(temp2);
        m[temp2].push_back(temp1);
    }
    
    temp1 = new vertex;
    temp2 = new vertex;

    cout << "\nPrinting map: " << endl;
    for( it = m.begin(); it != m.end(); it++ )
    {
        temp = (*it).first;

        cout << temp->index << "\t";

        v = (*it).second;
        for( int i = 0; i < v.size(); i++ )
        {
            temp1 = v[i];
            cout << temp1->index << "\t";
        }
        cout << "\n";
        v.clear();
    }
    for( it = m.begin(); it != m.end(); it++ )
    {
        temp = (*it).first;
        v.push_back(temp);
    }
    return 0;
}

The output that I am getting now is:

Enter no. of edges: 4

Enter source: 1

Enter dest: 3

Enter source: 4

Enter dest: 3

Enter source: 4

Enter dest: 2

Enter source: 2

Enter dest: 1

Printing map: 
1   3   
3   1   
4   3   
3   4   
4   2   
2   4   
2   1   
1   2   

But it should be:

1 3 2  
2 4 1      
3 1 4  
4 3 2

Where am I going wrong?

The std::map will compare the type you give it as key ( vertex* ), but you define the < operator on vertex.

You can use the struct themselves as keys, or -if you have to use pointers- you have to give the map a way to compare the pointers.

Now, std::map use the std::less as a comparison predicate, that is defined in therm of < (that's why using the struct themselves, you can achieve the result by overloading < ).

You can either:

o) define yourself a predicate that compares vertex*: it can be

template <class T> //assume T is a pointer or pointer-like class
struct redirected_less : public std::binary_function <T,T,bool> 
{
    bool operator() (const T& x, const T& y) const {return *x < *y;}
};

and then define you map as

std::map<vertex*, vector<vertex*>,  redirected_less<vertex*> >

o) specialize the std::less for vertex* as

namespace std
{
     template <> 
     struct less<vertex*> : binary_function <vertex*,vertex*,bool> 
     {
         bool operator() (vertex* x, vertex* y) const {return *x < *y; }
     };
}

and declare your map as

std::map<vertex*, vector<vertex*> >

as usual.

I personally prefer the first (gives a more localized code, less "arcane" in future readings)

You can't use pointers as keys. If you have two structures, that are "the same" according to your rules, but they are allocated on the heap with new , then their pointers will never be the same.

Use the structures, not their pointers, as key.

map<vertex*, vector<vertex*> > m;// its key type vertex*  

m uses

bool operator < (vertex * const, vertex* const) ;

while ordering

So you need to overload

bool operator < (vertex * const, vertex* const); 

we have a problem here. we can't overload on pointer. we could provide our own Compare function like this:

struct cmp{ 
      bool operator ()(vertex * const first, vertex* const second)
      { return first.index < second->index;} 
 };
cmp _cmp;
map<vertex*, vector<vertex*>, cmp> m(_cmp);

C++11

Emilio Garavaglia analyzed the problem with your code and how to fix it very well. However, if you can use C++11 features, then you can modernize your solution a bit. For example, you can use a lambda expression instead of defining operator< for your struct, and pass it to the map's constructor. This approach is useful if you can't modify the struct that you want to store in your map or if you want to provide different comparison functions for different maps. Range-based for loops save you from having to deal with iterators and the placeholder auto saves you from having to specify lengthy container element types. I'd also like to mention that you don't need to specify operator== for your map to work.

Solution with vertex as key

For the sake of clarity I replaced the user input with hard-coded values:

struct vertex {
    int color;
    vertex *pi;
    int index;
};

int main() {
    auto comp = [](const vertex& v1, const vertex& v2) { return v1.index < v2.index; };
    std::map<vertex, std::vector<vertex>, decltype(comp)> m(comp);

    // Replace user input.
    std::vector<std::pair<int, int>> edges = { {1, 3}, {4, 3}, {4, 2}, {2, 1} };

    // Fill the map.
    for(auto const &e : edges) {
        vertex temp1;
        vertex temp2;
        temp1.index = e.first;
        temp2.index = e.second;
        m[temp1].push_back(temp2);
        m[temp2].push_back(temp1);
    }

    // Print the map.
    std::cout << "Printing map:" << std::endl;
    for (auto const &kv : m) {
        std::cout << kv.first.index << " =>";
        for (auto const &v : kv.second)
            std::cout << " " << v.index;
        std::cout << std::endl;
    }

    return 0;
}

Output:

Printing map:
1 => 3 2
2 => 4 1
3 => 1 4
4 => 3 2

Note: If you can use C++17 , then you can further shorten the code for printing the map by using structured binding , as shown here on Coliru .

Solution with vertex* as key

Basically, you have to replace all occurrences of vertex in the above code with vertex* . In addition, you have to ensure that the lambda expression does not compare the given pointers, but the content of the structs to which the pointers refer:

auto comp = [](const vertex* pv1, const vertex* pv2) { return pv1->index < pv2->index; };
std::map<vertex*, std::vector<vertex*>, decltype(comp)> m(comp);

You also have to make sure that you delete all dynamically allocated vertices at the end. For example, you can empty your map properly in the following way:

for (auto const &kv : m)
    for (auto const pv : kv.second)
        delete pv;
m.clear();

Code on Coliru

Note: In order to ease memory management, you should prefer to store smart pointers in the map, such as std::unique_ptr or std::shared_ptr .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM