简体   繁体   中英

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.

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. 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. (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?

Unsorted MWE

This is a MWE without any attempt at sorting. 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 . In my real application, I can't guarantee that the addresses of these vertices will follow id ordering.

Live On 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.

  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. 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; 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

#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.

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

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? . 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).

However the problem is, of course, that the vertex_descriptor values themselves are not deterministic here.

BGL also shows how to add a custom vertex property tag to the vertex; 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:

  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)

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 ²:

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:

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 . Note I'm being extra-careful here in debug-mode so we assert that graph is actually set consistently for our edges ³.

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. Sadly, prohibiting EdgeInfo 's copy constructor breaks the standard add_edge implementation because if doesn't allow move semantics. Again, this lives in a detail namespace, so the only real way to hack it is to contribute changes to BGL

A little helper to prevent forgetting to add the EdgeInfo property for new edges (using c++14 for brevity):

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

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. 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 :)

³ it's easy to forget about it, but we added a custom constructor so we can't have default-constructed EdgeInfo s lying around.

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