使用带有自定义 class 的 boost 图形库作为顶点
Using boost graph library with a custom class for vertices
我目前正在尝试在现有的 c++ 项目中使用 boost 图形库。我想在提升图中存储自定义 class 的对象。下面是一个带有两个成员(一个字符串和一个 int)的自定义 class 定义及其对应的 getter 方法的小示例。
我有几个问题:
- 如何在 Graphviz 输出中包含字符串和整数值?我发现这个 answer 与使用
boost::make_label_writer
的类似问题有关,但我不确定我的示例是否可以适用于此(我使用的是自定义 class 和共享指针)。
- 同一个对象(相同的字符串和 int 值)在图中的存储次数不应超过一次。因此,我在自定义 class 中重载了两个比较运算符。我还读到我必须将图形类型定义的第二个模板参数更改为
boost::setS
但这会导致编译器出现非常长的错误消息 ...
假设我创建了一个自定义的新对象class:如何检查它是否已存储在图中?
#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;
}
程序输出:
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 ;
}
不改变你的声明,这有点痛苦,但可能:
{
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);
}
digraph G {
123 [label=Lorem];
456 [label=ipsum];
123 [label=Lorem];
123->456 ;
123->123 ;
}
您需要从外部处理这个问题。为什么不拥有一组侵入式节点并以这种方式验证约束。如您所说更改顶点容器选择器没有任何效果(它只是以升序存储顶点描述符,而它们像以前一样保持唯一)。
副作用是从连续分配的顶点存储变为基于节点的存储(优点:iterator/reference 稳定性,缺点:分配开销,减少引用的局部性,非隐式 vertex_index)。后者是罪魁祸首:BGL 中的许多东西都需要一个顶点索引,如果没有暗示(通过使用 vecS
例如),你必须传递一个。
有趣的是,由于我将 write_graphviz_dp
与特定的 node_id
属性 一起使用,所以现在不需要隐式顶点索引,所以您 可以 将 vecS
更改为 setS
并观察行为:Live On Coliru
我觉得现在不是检查的时候。没有比访问所有顶点更好的方法了,除非您保持外部索引更新。
Style/Simplification
因为 std::shared_ptr
意味着你有 c++11,让我们使用它。
此外,带有自定义 属性 的整个舞蹈主要是一种更笨拙的方式 属性 捆绑:这些在句法上更容易且得到更好的支持。
看区别:
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);
}
}
比较:没有共享指针
我并不是说你不能使用它,但我也不相信你需要它。所以,让我们删除它,以便您看到不同之处:
#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);
}
这大约是代码的一半。从这里我们可以探索如何添加所需的功能
独特性:让我们来试试我们的手
最简单的做法是在添加新节点之前检查现有节点:
#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);
}
请注意 v3
现在如何等于 v1
:
digraph G {
123 [label=Lorem];
456 [label=ipsum];
123->456 ;
123->123 ;
}
我目前正在尝试在现有的 c++ 项目中使用 boost 图形库。我想在提升图中存储自定义 class 的对象。下面是一个带有两个成员(一个字符串和一个 int)的自定义 class 定义及其对应的 getter 方法的小示例。
我有几个问题:
- 如何在 Graphviz 输出中包含字符串和整数值?我发现这个 answer 与使用
boost::make_label_writer
的类似问题有关,但我不确定我的示例是否可以适用于此(我使用的是自定义 class 和共享指针)。 - 同一个对象(相同的字符串和 int 值)在图中的存储次数不应超过一次。因此,我在自定义 class 中重载了两个比较运算符。我还读到我必须将图形类型定义的第二个模板参数更改为
boost::setS
但这会导致编译器出现非常长的错误消息 ... 假设我创建了一个自定义的新对象class:如何检查它是否已存储在图中?
#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; }
程序输出:
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 ;
}
不改变你的声明,这有点痛苦,但可能:
{ 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); }
digraph G { 123 [label=Lorem]; 456 [label=ipsum]; 123 [label=Lorem]; 123->456 ; 123->123 ; }
您需要从外部处理这个问题。为什么不拥有一组侵入式节点并以这种方式验证约束。如您所说更改顶点容器选择器没有任何效果(它只是以升序存储顶点描述符,而它们像以前一样保持唯一)。
副作用是从连续分配的顶点存储变为基于节点的存储(优点:iterator/reference 稳定性,缺点:分配开销,减少引用的局部性,非隐式 vertex_index)。后者是罪魁祸首:BGL 中的许多东西都需要一个顶点索引,如果没有暗示(通过使用
vecS
例如),你必须传递一个。有趣的是,由于我将
write_graphviz_dp
与特定的node_id
属性 一起使用,所以现在不需要隐式顶点索引,所以您 可以 将vecS
更改为setS
并观察行为:Live On Coliru我觉得现在不是检查的时候。没有比访问所有顶点更好的方法了,除非您保持外部索引更新。
Style/Simplification
因为 std::shared_ptr
意味着你有 c++11,让我们使用它。
此外,带有自定义 属性 的整个舞蹈主要是一种更笨拙的方式 属性 捆绑:这些在句法上更容易且得到更好的支持。
看区别:
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);
}
}
比较:没有共享指针
我并不是说你不能使用它,但我也不相信你需要它。所以,让我们删除它,以便您看到不同之处:
#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);
}
这大约是代码的一半。从这里我们可以探索如何添加所需的功能
独特性:让我们来试试我们的手
最简单的做法是在添加新节点之前检查现有节点:
#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);
}
请注意 v3
现在如何等于 v1
:
digraph G {
123 [label=Lorem];
456 [label=ipsum];
123->456 ;
123->123 ;
}