简体   繁体   English

BGL adjacency_list:如何使用vertex属性而不是描述符对out_edges进行排序

[英]BGL adjacency_list: How to sort out_edges using vertex property, not descriptor

Using BGL adjacency_list, I would like the out-edges from a vertex to be sorted by a property of the target vertex. 使用BGL adjacency_list,我希望顶点的外边缘按目标顶点的属性排序。

In BGL, the out-edge list is already sorted by target vertex descriptor, but I happen to be using a listS as my vertex container, so my vertex descriptors are void* pointers. 在BGL中,外边列表已经按目标顶点描述符排序,但我碰巧使用listS作为我的顶点容器,所以我的顶点描述符是void*指针。 Unfortunately, I've found that sorting by these addresses makes the ordering of my graph traversal indeterministic. 不幸的是,我发现按这些地址排序会使我的图表遍历的顺序不确定。 My vertices do have a custom property class which has an ID that could be used for sorting the out-edges, but I haven't been able to make it work. 我的顶点有一个自定义属性类,它有一个可用于排序边缘的ID,但我无法使其工作。 (See code below.) (见下面的代码。)

I've referred to this question, which was helpful in diagnosing the problem, but I didn't see a good solution in it: boost graph library: deterministic order of iteration of in_edges? 我已经提到过这个问题,这有助于诊断问题,但我没有看到一个很好的解决方案: boost图库:in_e​​dges迭代的确定性顺序?

Unsorted MWE 未分类的MWE

This is a MWE without any attempt at sorting. 这是一个没有任何排序尝试的MWE。 Note that I create the vertices with id in the order 0, 5, 2, 8 to simulate the vertex addresses being in a different order than the id . 请注意,我按照0,5,2,8的顺序创建id的顶点,以模拟顶点地址的顺序与id不同。 In my real application, I can't guarantee that the addresses of these vertices will follow id ordering. 在我的实际应用程序中,我不能保证这些顶点的地址将遵循id排序。

Live On Coliru 住在Coliru

#include <iostream>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/directed_graph.hpp>

class VertexInfo
{
public:
    VertexInfo(int i) : id(i) {}
    int id;
};

int main()
{
    typedef boost::adjacency_list< boost::setS,
                                   boost::listS,    
                                   boost::bidirectionalS,
                                   VertexInfo,
                                   boost::no_property,
                                   boost::no_property,
                                   boost::listS
                                 > Graph;

    //typedef boost::graph_traits<Graph>::edge_descriptor Edge;
    typedef boost::graph_traits<Graph>::vertex_descriptor Vertex;

    Graph g;

    Vertex src  = boost::add_vertex(VertexInfo(0), g);
    Vertex tar1 = boost::add_vertex(VertexInfo(5), g);
    Vertex tar2 = boost::add_vertex(VertexInfo(2), g);
    Vertex tar3 = boost::add_vertex(VertexInfo(8), g);

    boost::add_edge(src, tar1, g);
    boost::add_edge(src, tar2, g);
    boost::add_edge(src, tar3, g);

    // If sorted by address, the order would probably be:
    // 0 --> 5
    // 0 --> 2
    // 0 --> 8
    // If sorted by ID, the order should be:
    // 0 --> 2
    // 0 --> 5
    // 0 --> 8

    typename boost::graph_traits<Graph>::out_edge_iterator ei, ei_end;
    for(boost::tie(ei, ei_end) = boost::out_edges(src, g); ei != ei_end; ++ei)
    {
        std::cout << g[boost::source(*ei, g)].id 
                  << " --> " 
                  << g[boost::target(*ei, g)].id 
                  << std::endl;
    }

    return 0; 
}

What this gives me currently is: 这给我的目前是:

0 --> 5
0 --> 2
0 --> 8

But I need it to give me this: 但我需要它给我这个:

0 --> 2
0 --> 5
0 --> 8

Attempt at sorting, not working 尝试排序,而不是工作

I looked to the Boost documentation and found these two parts helpful. 我查看了Boost文档,发现这两个部分很有用。

  1. BGL provides an example on how to order the out-edges ( ordered_out_edges.cpp ) by creating a custom container selector and using a custom comparator for that container. BGL通过创建自定义容器选择器并为该容器使用自定义比较器,提供了如何订购外边缘( ordered_out_edges.cpp )的示例。 Unfortunately, comparator in the example uses the target vertex descriptor and a default property of the edge in order to make the comparison. 不幸的是,示例中的比较器使用目标顶点描述符和边缘的默认属性来进行比较。 I would need a comparator that compares based on a custom property of the target vertices; 我需要一个基于目标顶点的自定义属性进行比较的比较器; without access to the graph object. 无法访问图形对象。

  2. BGL also shows how to add a custom vertex property tag to the vertex; BGL还展示了如何向顶点添加自定义顶点属性标记 ; I had hoped that I could access a tagged vertex property from the vertex descriptor without having the graph object; 我原本希望我可以从顶点描述符访问一个标记的顶点属性而不需要图形对象; but that doesn't seem to work either 但这似乎也不起作用

Live On Coliru 住在Coliru

#include <iostream>
#include <functional>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/directed_graph.hpp>

// http://www.boost.org/doc/libs/1_55_0/libs/graph/example/ordered_out_edges.cpp
// http://www.boost.org/doc/libs/1_55_0/libs/graph/doc/using_adjacency_list.html#sec:custom-vertex-properties
// https://stackoverflow.com/questions/30968690/boost-graph-library-deterministic-order-of-iteration-of-in-edges
// http://www.boost.org/doc/libs/1_55_0/libs/graph/doc/adjacency_list.html

// ??
// https://stackoverflow.com/questions/9169276/bgl-edgeu-v-g-with-custom-associative-container-for-edge-lists

#ifdef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION
#error BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION not supported
#endif

// See custom-vetex-properties
struct vertex_info_t
{
    typedef boost::vertex_property_tag kind;
};

class VertexInfo
{
public:
    VertexInfo(int i) : id(i) {}
    int id;
};

////////////////////////////////////////
// See libs/graph/example/ordered_out_edges.cpp
template <class StoredEdge>
struct Comparator : public std::binary_function<StoredEdge, StoredEdge, bool>
{
    bool operator()(const StoredEdge& e1, const StoredEdge& e2)
    {
        return boost::get(vertex_info_t(), e1.get_target()).id < boost::get(vertex_info_t(), e2.get_target()).id;
        //return e2.get_target() < e1.get_target(); // reverse order of insertion, an example to prove custom OrderedSetS but does not use vertex properties
    }
};
struct OrderedSetS {};
namespace boost
{
    template <class ValueType>
    struct container_gen<OrderedSetS, ValueType>
    {
        typedef std::set<ValueType, Comparator<ValueType> > type;
    };
    template <>
    struct parallel_edge_traits<OrderedSetS>
    { 
        typedef allow_parallel_edge_tag type;
    };
}
////////////////////////////////////////

int main()
{
    typedef boost::adjacency_list< OrderedSetS, //boost::setS,
                                   boost::listS,    
                                   boost::bidirectionalS,
                                   boost::property<vertex_info_t, VertexInfo>, //VertexInfo,
                                   boost::no_property,
                                   boost::no_property,
                                   boost::listS
                                 > Graph;

    //typedef boost::graph_traits<Graph>::edge_descriptor Edge;
    typedef boost::graph_traits<Graph>::vertex_descriptor Vertex;

    Graph g;

    Vertex src  = boost::add_vertex(VertexInfo(0), g);
    Vertex tar1 = boost::add_vertex(VertexInfo(5), g);
    Vertex tar2 = boost::add_vertex(VertexInfo(2), g);
    Vertex tar3 = boost::add_vertex(VertexInfo(8), g);

    boost::add_edge(src, tar1, g);
    boost::add_edge(src, tar2, g);
    boost::add_edge(src, tar3, g);

    // If sorted by address, the order would probably be:
    // 0 --> 5
    // 0 --> 2
    // 0 --> 8
    // If sorted by ID, the order should be:
    // 0 --> 2
    // 0 --> 5
    // 0 --> 8

    typename boost::graph_traits<Graph>::out_edge_iterator ei, ei_end;
    for(boost::tie(ei, ei_end) = boost::out_edges(src, g); ei != ei_end; ++ei)
    {
        std::cout << boost::get( boost::get(vertex_info_t(), g), boost::source(*ei, g) ).id 
                  << " --> " 
                  << boost::get( boost::get(vertex_info_t(), g), boost::target(*ei, g) ).id 
                  << std::endl;
    }

    return 0; 
}

This results in a compilation error 这会导致编译错误

main.cpp:38:26: error: no matching function for call to 'get(vertex_info_t, void*&)'
         return boost::get(vertex_info_t(), e1.get_target()).id < boost::get(vertex_info_t(), e2.get_target()).id;

It doesn't allow me to get the VertexInfo property class via vertex_info_t tag with only a vertex descriptor ( void* ) and without the graph. 它不允许我通过vertex_info_t标记获取VertexInfo属性类,只有顶点描述符( void* )且没有图形。

Question

Can anyone think of a way to sort the out-edges by either external or internal-tagged properties of the target vertices? 任何人都可以想到一种通过目标顶点的外部或内部标记属性对外边缘进行排序的方法吗?

In BGL, the out-edge list is already sorted by target vertex descriptor 在BGL中,外边列表已经按目标顶点描述符排序

Haven't checked, but right off the bat I'd say that's an undocumented implementation detail anyways, not something to be relied on (unless you can point to where in the documentation this is stated). 没有检查过,但是马上我会说这是一个没有文档的实现细节,而不是可以依赖的东西(除非你可以指出在文档中的哪个地方说明)。

UPDATE Thanks to @EvanW I now remember I did check this earlier: boost graph library: deterministic order of iteration of in_edges? 更新感谢@EvanW我现在记得我之前检查过: boost图库:in_e​​dges迭代的确定性顺序? . So, given an ordered OutEdgeListS you can rely on the out-edges being ordered by vertex descriptor (until a newer version of BGL changes that implementation detail, possibly). 因此,给定一个有序的OutEdgeListS可以依赖顶点描述符排序的外边缘(直到更新版本的BGL可能更改该实现细节)。

However the problem is, of course, that the vertex_descriptor values themselves are not deterministic here. 然而,问题当然是vertex_descriptor值本身在这里不是确定性的。

BGL also shows how to add a custom vertex property tag to the vertex; BGL还展示了如何向顶点添加自定义顶点属性标记; I had hoped that I could access a tagged vertex property from the vertex descriptor without having the graph object; 我原本希望我可以从顶点描述符访问一个标记的顶点属性而不需要图形对象; but that doesn't seem to work either 但这似乎也不起作用

Indeed, you have pinpointed the problem: though the stored-edge objects have the property bundle right there (that's what makes them bundled ), the vertices are just referenced by their descriptors (which are opaque, as you noted). 实际上,你已经找到了问题:虽然存储边缘对象在那里有属性束(这就是它们捆绑的原因 ),但顶点只是被它们的描述符引用(如你所说,它们是不透明的)。

So the only workaround is to somehow have the graph object available. 所以唯一的解决方法是以某种方式使图形对象可用。 Since adjacency_list<> does not provide a way to customize how the edge-containers are constructed¹, there are roughly two ways: 由于adjacency_list<>没有提供定制边缘容器构造方式的方法¹,大致有两种方法:

  1. use a global reference, which of course has the downsides of not supporting more than one instance of the graph type. 使用全局引用,当然其缺点是不支持多个图形类型的实例。 I'd strongly recommend against this because it would break too easily (eg possibly already when you pass a graph by value) and limits the graph's use in other ways (eg you will not be able to use subgraphs). 我强烈建议不要这样做,因为它会太容易破坏(例如,当您按值传递图形时可能已经破坏)并且以其他方式限制图形的使用(例如,您将无法使用子图)。

  2. store a redundant graph-reference inside the edge properties. 在边缘属性中存储冗余图形引用。 This is rather wasteful, but it does the job, at least for this simple test case (see CAVEAT below) 这是相当浪费的,但它确实起到了作用,至少对于这个简单的测试用例(参见下面的CAVEAT)

Proof Of Concept 概念证明

The approach 2. presents one more technical hurdle, and that's the requirement that you can forward declare Graph so you can actually store a typed pointer inside EdgeInfo ²: 方法2.提出了另一个技术障碍,那就是你可以转发声明Graph以便在EdgeInfo实际存储一个类型指针的EdgeInfo

struct ForwardGraph;

struct VertexInfo
{
    VertexInfo(int i) : id(i) {}
    int id;
};

struct EdgeInfo {
    ForwardGraph const* graph;
    EdgeInfo(ForwardGraph const& g) : graph(&g) {}
    //EdgeInfo(EdgeInfo const&) = delete;
    EdgeInfo& operator=(EdgeInfo const&) = delete;
};

Now, define the ForwardGraph somewhat indirectly: 现在,间接定义ForwardGraph

typedef boost::adjacency_list<by_idS, boost::listS, boost::bidirectionalS, VertexInfo, EdgeInfo> GraphImpl;

struct ForwardGraph : GraphImpl {
    using GraphImpl::GraphImpl; // inherit constructors
};

The crux, of course is, how to wire up by_idS . 当然,关键是如何连接by_idS Note I'm being extra-careful here in debug-mode so we assert that graph is actually set consistently for our edges ³. 注意我在调试模式下要特别小心,所以我们断言graph实际上是为我们的边缘设置的。

struct by_idS { };

namespace boost {
    template <class T>
    struct container_gen<by_idS, T> {
        struct cmp {
            bool operator()(const T& e1, const T& e2) const {
                auto const* g1 = get(&EdgeInfo::graph, e1);
                auto const* g2 = get(&EdgeInfo::graph, e2);
                assert(g1 && g2 && g1 == g2);

                auto& g = *g1;
                return g[e1.get_target()].id < g[e2.get_target()].id;
            }
        };

        typedef std::multiset<T, cmp> type;
    };

    template <> struct parallel_edge_traits<by_idS> { typedef allow_parallel_edge_tag type; };
}

CAVEAT: I wouldn't be absolutely convinced that no algorithm of Boost's is going to copy edge properties when graphs are copied. CAVEAT:我不会绝对相信在复制图形时没有Boost的算法会复制边缘属性。 Sadly, prohibiting EdgeInfo 's copy constructor breaks the standard add_edge implementation because if doesn't allow move semantics. 遗憾的是,禁止EdgeInfo的复制构造函数会破坏标准的add_edge实现,因为如果不允许移动语义。 Again, this lives in a detail namespace, so the only real way to hack it is to contribute changes to BGL 同样,它存在于一个详细的命名空间中,因此唯一真正的破解方法是为BGL提供更改

A little helper to prevent forgetting to add the EdgeInfo property for new edges (using c++14 for brevity): 一个小帮手,以防止忘记为新边添加EdgeInfo属性(为简洁起见使用c ++ 14):

namespace boost {
    auto add_edge(GraphImpl::vertex_descriptor src, GraphImpl::vertex_descriptor tgt, ForwardGraph& g) {
        return add_edge(src, tgt, EdgeInfo { g }, g);
    }
}

And we're ready to roll: 我们已经准备好了:

Live On Coliru 住在Coliru

typedef ForwardGraph Graph;

int main() {
    Graph g;

    auto src  = boost::add_vertex({0}, g);
    auto tar1 = boost::add_vertex({5}, g);
    auto tar2 = boost::add_vertex({2}, g);
    auto tar3 = boost::add_vertex({8}, g);

    boost::add_edge(src, tar1, g);
    boost::add_edge(src, tar2, g);
    boost::add_edge(src, tar3, g);

    typename boost::graph_traits<Graph>::out_edge_iterator ei, ei_end;
    for(auto e : boost::make_iterator_range(boost::out_edges(src, g)))
        std::cout << g[boost::source(e, g)].id << " --> " << g[boost::target(e, g)].id << std::endl;
}

Prints 打印

0 --> 2
0 --> 5
0 --> 8

¹ you could not even hack it by specializing the types (namely adj_list_gen<>::struct config ) in the detail namespaces, because everything assumes StoredEdge s can be either default-constructed or constructed from a EdgeProperty object. ¹你甚至无法通过在细节命名空间中专门化类型(即adj_list_gen<>::struct config )来破解它,因为所有假定StoredEdge可以是默认构造的,也可以是从EdgeProperty对象构造的。 There is no way to inject a reference to the graph. 没有办法注入图表的引用。

² again, you can't cheat, because even if you stored a void const* there would be no way to actually cast to the Graph type inside the comparator for your edge-set, because ... the type couldn't be known before declaring the Graph :) ²再次,你不能作弊,因为即使你存储了一个void const* ,也没有办法在比较器内为你的边集设置实际的Graph类型,因为......这个类型是不可知的在声明Graph之前:)

³ it's easy to forget about it, but we added a custom constructor so we can't have default-constructed EdgeInfo s lying around. ³很容易忘记它,但我们添加了一个自定义构造函数,因此我们不能使用默认构造的EdgeInfo

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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