Note: I changed the title of the question because it turns out the following text and minimal example reflect my original problem poorly. The original title was "Interchanging two similar indices in a boost multi-index container"
I'm implementing directed graphs (with loops and multiple edges) whose vertices are numbered and where we can view the edges sorted by source vertex or target vertex. To do this, I'm using a boost::multi_index_container
that stores each edge, with two ordered, non-unique, member key extractors: source
and target
. (I don't think this is possible directly with BGL, but please let me know if it is!)
In addition, I would like to be able to take a graph and reverse all of its edges (or create a new graph with the reverse edges). I can do this by iterating over the original edges and adding each one's reverse to a new container. However, this means that boost
has to recompute everything for every edge for at least one of the indices, which is quasilinear in the number of edges. Is there a way to tell boost
that it can reuse the knowledge it already has about the source
and target
of the original edges in order to place the reversed edges?
Here is a minimal example:
#include <iostream>
#include <vector>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>
// A directed edge
class Edge {
public:
Edge(int s, int t) :
source(s),
target(t)
{ }
int source;
int target;
// For display purposes
friend std::ostream& operator<<(std::ostream& os, const Edge& edge) {
os << "(" << edge.source << "," << edge.target << ")" << std::flush;
return os;
}
};
// Tags for the multi-index container
struct Source { };
struct Target { };
// Directed graphs with loops and multiple edges, with the possibility of
// viewing the edges sorted by source or by target.
using Directed_graph = boost::multi_index_container<
Edge,
boost::multi_index::indexed_by<
boost::multi_index::ordered_non_unique<
boost::multi_index::tag< Source >,
boost::multi_index::member< Edge, int, &Edge::source >
>,
boost::multi_index::ordered_non_unique<
boost::multi_index::tag< Target >,
boost::multi_index::member< Edge, int, &Edge::target >
>
>
>;
// Reverse all the edges of the graph, either in place or creating a copy
//
// QUESTION: Is there a better way to do this?
//
Directed_graph reverse_graph(Directed_graph& graph) {
Directed_graph reversed_graph;
for (const auto& edge : graph) {
reversed_graph.insert(Edge(edge.target, edge.source));
}
return reversed_graph;
}
// Print every edge of the graph
void output(const Directed_graph& graph) {
for (const auto& edge : graph) {
std::cout << edge << " " << std::flush;
}
std::cout << std::endl;
}
int main() {
Directed_graph G;
G.insert(Edge(0, 1));
G.insert(Edge(1, 2));
G.insert(Edge(1, 3));
G.insert(Edge(3, 0));
std::cout << "Directed graph:" << std::endl;
output(G);
std::cout << "Reversed directed graph:" << std::endl;
Directed_graph rG = reverse_graph(G);
output(rG);
return 0;
}
Compiling with gcc -std=c++11
, I get the output
Directed graph:
(0,1) (1,2) (1,3) (3,0)
Reversed directed graph:
(0,3) (1,0) (2,1) (3,1)
To sum up, is there a way to write the function reverse_graph
with less than quasilinear complexity? Ideally, this would be a constant time operation.
One potential direction would be the ability to insert elements with simultaneous hints for multiple indices, but I haven't found a function like that either (and even then, I don't see how to get to constant time).
Note: Just a technicality, but Directed_graph
doesn't completely encode a directed graph, since we would also need to know how many vertices there are in total. This isn't an issue in the real code, and I hope it doesn't obstruct the above example!