Reputation: 1343
I am currently trying to use the boost graph library in an existing c++ project. I would like to store objects of a custom class in a boost graph. Below is a small example with a custom class definition with two members (a string and an int) and their corresponding getter methods.
I have a few questions:
boost::make_label_writer
but I not really sure if my example can be adopted to this (I am using a custom class and shared pointers).boost::setS
but that results in a really long error message of the compiler ...Let's say I created a new object of my custom class: How can I check if it is already stored in the graph?
#include <iostream>
#include <boost/graph/graphviz.hpp>
class my_custom_class {
public:
my_custom_class(const std::string &my_string,
int my_int) : my_string(my_string),
my_int(my_int) {}
virtual ~my_custom_class() {
}
std::string get_my_string() const {
return my_string;
}
int get_int() const {
return my_int;
}
bool operator==(const my_custom_class &rhs) const {
return my_string == rhs.my_string &&
my_int == rhs.my_int;
}
bool operator!=(const my_custom_class &rhs) const {
return !(rhs == *this);
}
private:
std::string my_string;
int my_int;
};
namespace boost {
enum vertex_my_custom_class_t {
vertex_my_custom_class = 123
};
BOOST_INSTALL_PROPERTY(vertex,
my_custom_class);
}
int main() {
typedef boost::adjacency_list<boost::vecS,
boost::vecS,
boost::directedS,
boost::property<boost::vertex_my_custom_class_t,
std::shared_ptr<my_custom_class>>> graph_t;
typedef boost::graph_traits<graph_t>::vertex_descriptor vertex_t;
std::shared_ptr<my_custom_class> object_one = std::make_shared<my_custom_class>("Lorem", 123);
std::shared_ptr<my_custom_class> object_two = std::make_shared<my_custom_class>("ipsum", 456);
std::shared_ptr<my_custom_class> object_three = std::make_shared<my_custom_class>("Lorem", 123);
std::cout << "object one: " << object_one->get_int() << "; " << object_one->get_my_string() << std::endl;
std::cout << "object two: " << object_two->get_int() << "; " << object_two->get_my_string() << std::endl;
std::cout << "object three: " << object_three->get_int() << "; " << object_three->get_my_string() << std::endl;
std::cout << std::endl;
std::cout << "object one == object two: " << (*object_one == *object_two) << std::endl;
std::cout << "object one == object three: " << (*object_one == *object_three) << std::endl;
std::cout << std::endl;
graph_t graph;
vertex_t vertex_one = boost::add_vertex(object_one, graph);
vertex_t vertex_two = boost::add_vertex(object_two, graph);
vertex_t vertex_three = boost::add_vertex(object_three, graph);
boost::add_edge(vertex_one, vertex_two, graph);
boost::add_edge(vertex_one, vertex_three, graph);
boost::write_graphviz(std::cout, graph);
return 0;
}
Program output:
object one: 123; Lorem
object two: 456; ipsum
object three: 123; Lorem
object one == object two: 0
object one == object three: 1
digraph G {
0;
1;
2;
0->1 ;
0->2 ;
}
Upvotes: 3
Views: 1814
Reputation: 392929
without changing your declarations, this is a bit painful, but possible:
{
boost::dynamic_properties dp;
boost::property_map<graph_t, boost::vertex_my_custom_class_t>::type custom = get(boost::vertex_my_custom_class, graph);
dp.property("node_id", boost::make_transform_value_property_map(std::mem_fn(&my_custom_class::get_int), custom));
dp.property("label", boost::make_transform_value_property_map(std::mem_fn(&my_custom_class::get_my_string), custom));
boost::write_graphviz_dp(std::cout, graph, dp);
}
Printing: Live On Coliru
digraph G {
123 [label=Lorem];
456 [label=ipsum];
123 [label=Lorem];
123->456 ;
123->123 ;
}
You will want to take care of this externally. Why not have an intrusive set of nodes and verify the contraints that way. Changing the Vertex Container Selector as you say has no effect (it just ends up storing the vertex descriptors in ascending order, while they stay unique as they were before).
A side effect is changing from contiguously allocated vertex storage to node-based (pro: iterator/reference stability, con: allocation overhead, reduced locality of reference, non-implicit vertex_index). The latter is the culprit: many things in BGL require a vertex index and if one is not implied (by using vecS
e.g.) you have to pass one.
Interestingly, since I used write_graphviz_dp
with a specific node_id
property there's no need for the implicit vertex index just now, so you could change vecS
to setS
and observe the behaviour: Live On Coliru
I think that's not the right time to check. There is not really a better way than to visit all vertices, unless you keep an external index updated.
Since std::shared_ptr
implies you have c++11, let's use it.
Also, the whole dance with a custom property is mostly a way more clumsy of doing property bundles: these are syntactically easier and better supported.
See the difference:
Note I was over-enthusiastic for a second, using a shared-ptr you still require the the transform-value-property-map:
#include <boost/graph/graphviz.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <iostream>
struct MyVertex {
MyVertex(std::string label, int id) : _label(std::move(label)), _id(id) {}
std::string label() const { return _label; }
int id() const { return _id; }
bool operator<(const MyVertex &rhs) const { return std::tie(_id, _label) < std::tie(rhs._id, rhs._label); }
private:
std::string _label;
int _id;
};
using graph_t = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, std::shared_ptr<MyVertex>>;
int main() {
graph_t graph;
auto v1 = add_vertex(std::make_shared<MyVertex>("Lorem", 123), graph);
auto v2 = add_vertex(std::make_shared<MyVertex>("ipsum", 456), graph);
auto v3 = add_vertex(std::make_shared<MyVertex>("Lorem", 123), graph);
add_edge(v1, v2, graph);
add_edge(v1, v3, graph);
{
boost::dynamic_properties dp;
auto bundle = get(boost::vertex_bundle, graph);
dp.property("node_id", make_transform_value_property_map(std::mem_fn(&MyVertex::id), bundle));
dp.property("label", make_transform_value_property_map(std::mem_fn(&MyVertex::label), bundle));
write_graphviz_dp(std::cout, graph, dp);
}
}
I'm not saying you must not use it, but I'm also not convinced you need it. So, let's remove it so you see the difference:
#include <boost/graph/graphviz.hpp>
#include <iostream>
struct MyVertex {
std::string label;
int id;
bool operator<(const MyVertex &rhs) const { return std::tie(id, label) < std::tie(rhs.id, rhs.label); }
};
using graph_t = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, MyVertex>;
int main() {
graph_t graph;
auto v1 = add_vertex({"Lorem", 123}, graph);
auto v2 = add_vertex({"ipsum", 456}, graph);
auto v3 = add_vertex({"Lorem", 123}, graph);
add_edge(v1, v2, graph);
add_edge(v1, v3, graph);
boost::dynamic_properties dp;
dp.property("node_id", boost::get(&MyVertex::id, graph));
dp.property("label", boost::get(&MyVertex::label, graph));
write_graphviz_dp(std::cout, graph, dp);
}
That's roughly half the code. From here we can explore how to add the desired feature(s)
The simplest thing to do is to check the existing nodes before adding a new one:
#include <boost/graph/graphviz.hpp>
#include <boost/range/iterator_range.hpp>
#include <iostream>
struct MyVertex {
std::string label;
int id;
auto key() const { return std::tie(id,label); }
bool operator< (const MyVertex &rhs) const { return key() < rhs.key(); }
bool operator==(const MyVertex &rhs) const { return key() == rhs.key(); }
bool operator!=(const MyVertex &rhs) const { return key() != rhs.key(); }
};
using graph_t = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, MyVertex>;
int main() {
graph_t graph;
auto node = [&graph](std::string name, int id) {
for (auto&& v : boost::make_iterator_range(vertices(graph)))
if (graph[v] == MyVertex{name, id})
return v;
return add_vertex({name, id}, graph);
};
auto v1 = node("Lorem", 123);
auto v2 = node("ipsum", 456);
auto v3 = node("Lorem", 123);
assert(v3==v1);
add_edge(v1, v2, graph);
add_edge(v1, v3, graph);
boost::dynamic_properties dp;
dp.property("node_id", boost::get(&MyVertex::id, graph));
dp.property("label", boost::get(&MyVertex::label, graph));
write_graphviz_dp(std::cout, graph, dp);
}
Note how v3
now equals v1
:
digraph G {
123 [label=Lorem];
456 [label=ipsum];
123->456 ;
123->123 ;
}
Upvotes: 5