I have to write code that can parse a file like this:
digraph G {
0[label="person" name="James Cameron"];
1[label="film" title="Avatar 2" year="2022"];
0->1 [label="directed"];
}
Each vertex has label necessarily, but also it may have an unknown number of properties. Moreover, these properties can be own for each vertex. With edges everything is simple - only one label. I would like not to write my own parser, but to use read_graphviz. Tell me, please, how can this be done?
It would be perfect if the code worked with such bundled property:
struct Vertex {
std::string label;
std::map<std::string, std::string> attributes;
};
I wrote a code that parses this:
digraph G {
0[label="person" attributes="name=James_Cameron"];
1[label="film" attributes="title=Avatar_2;year=2022"];
0->1 [label="directed"];
}
But this format looks creepy.
My code:
#include <iostream>
#include <vector>
#include <map>
#include <boost/graph/graphviz.hpp>
#include <boost/graph/adjacency_list.hpp>
struct Vertex {
std::string raw_attrs;
std::string label;
std::map<std::string, std::string> attributes;
};
struct Edge {
std::string label;
};
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::bidirectionalS, Vertex, Edge> Graph;
int main(int argc, char** argv) {
Graph graph;
boost::dynamic_properties dp(boost::ignore_other_properties);
dp.property("label", boost::get(&Vertex::label, graph));
dp.property("attributes", boost::get(&Vertex::raw_attrs, graph));
dp.property("label", boost::get(&Edge::label, graph));
std::ifstream f("file.txt");
boost::read_graphviz(f, graph, dp);
// transform raw_attrs to attributes
}
I promised to find the time to demonstrate the support for dynamic attributes in BGL read_graphviz.
I'll do that by implementing a roundtripping
int main() {
std::istringstream iss(R"(digraph G {
0[label="person" name="James Cameron"];
1[label="film" title="Avatar 2" year="2022"];
0->1 [label="directed"];
})");
auto g = do_read(iss);
do_write(std::cout, g);
}
Note that it's a logical roundtrip. It doesn't promise to preserve formatting or node id numbering.
0. Preliminaries
Some useful definitions:
struct Vertex {
int original_id;
std::map<std::string, std::string> attributes;
};
struct Edge {
std::string relation;
};
using G = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, Vertex, Edge>;
using V = G::vertex_descriptor;
1. Writing The Graph
The simpler part first. We use the overload that takes a writer interface. We can hack it in few lines:
void do_write(std::ostream& os, G& g) {
auto vw = boost::attributes_writer(get(&Vertex::attributes, g));
auto ew = boost::label_writer{get(&Edge::relation, g)};
write_graphviz(os, g, vw, ew);
}
We supply the vertex writer as the attributes writer, and write the one edge property (relation) as the label.
2. Reading The Graph
You can pass a generator function to the constructor of dynamic_properties. This will be invoked when a new attribute is found.
G do_read(std::istream& is) {
G g;
// ...
boost::dynamic_properties dp(newattr);
dp.property("label", get(&Edge::relation, g));
dp.property("node_id", get(&Vertex::original_id, g));
read_graphviz(is, g, dp);
return g;
}
Let's use a lambda to define the newattr generator function:
auto attrs = get(&Vertex::attributes, g);
auto newattr = [=](std::string const& name, auto&& descr, auto&&) -> Ptr {
if (typeid(V) == descr.type()) {
return make_dyn(boost::make_function_property_map<V>(
[=](V v) -> std::string& { return attrs[v][name]; }));
} else
return {};
};
Where make_dyn is a simple helper to wrap a concrete property map (like our function property map) in a shared pointer to a dynamic property map:
using Ptr = boost::shared_ptr<boost::dynamic_property_map>;
auto make_dyn = [](auto m) -> Ptr {
using DM = boost::detail::dynamic_property_map_adaptor<decltype(m)>;
auto sp = boost::make_shared<DM>(m);
return boost::static_pointer_cast<boost::dynamic_property_map>(sp);
};
In production code you would probably make this a free function, which would be more re-usable and slightly more readable by getting rid of the decltype expression.
Full Demo
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/property_map/function_property_map.hpp>
struct Vertex {
int original_id;
std::map<std::string, std::string> attributes;
};
struct Edge {
std::string relation;
};
using G = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS, Vertex, Edge>;
using V = G::vertex_descriptor;
void do_write(std::ostream& os, G& g) {
auto vw = boost::attributes_writer(get(&Vertex::attributes, g));
auto ew = boost::label_writer{get(&Edge::relation, g)};
write_graphviz(os, g, vw, ew);
}
G do_read(std::istream& is) {
using Ptr = boost::shared_ptr<boost::dynamic_property_map>;
auto make_dyn = [](auto m) -> Ptr {
using DM = boost::detail::dynamic_property_map_adaptor<decltype(m)>;
auto sp = boost::make_shared<DM>(m);
return boost::static_pointer_cast<boost::dynamic_property_map>(sp);
};
G g;
auto attrs = get(&Vertex::attributes, g);
auto newattr = [=](std::string const& name, auto&& descr, auto&&) -> Ptr {
if (typeid(V) == descr.type()) {
return make_dyn(boost::make_function_property_map<V>(
[=](V v) -> std::string& { return attrs[v][name]; }));
} else
return {};
};
boost::dynamic_properties dp(newattr);
dp.property("label", get(&Edge::relation, g));
dp.property("node_id", get(&Vertex::original_id, g));
read_graphviz(is, g, dp);
return g;
}
int main() {
std::istringstream iss(R"(digraph G {
0[label="person" name="James Cameron"];
1[label="film" title="Avatar 2" year="2022"];
0->1 [label="directed"];
})");
auto g = do_read(iss);
do_write(std::cout, g);
}
Prints
g++ -std=c++20 -O2 -pedantic -pthread main.cpp -lboost_graph && ./a.out
digraph G {
0[label=person, name="James Cameron"];
1[label=film, title="Avatar 2", year=2022];
0->1 [label=directed];
}
Writing a parser is not difficult. Here is the code for an application that parses the vertices. I hope you do not think it is creepy.
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
class cVertex
{
int userIndex;
std::string label;
std::map<std::string, std::string> attributes;
static std::vector<cVertex> myVertex;
public:
void read(const std::string &fname);
std::string textVertexList();
private:
void parseVertexLine(const std::string &line);
};
std::vector<cVertex> cVertex::myVertex;
void cVertex::read(const std::string &fname)
{
std::ifstream ifs(fname);
if (!ifs.is_open())
{
std::cout << "Cannot open file\n";
exit(1);
}
std::string line;
getline(ifs, line);
while (getline(ifs, line))
{
if (line.find("->") != -1)
break;
parseVertexLine(line);
}
}
std::string cVertex::textVertexList()
{
std::stringstream ss;
for (auto &v : myVertex)
{
ss << "\nvertex " << v.userIndex
<< " label is " << v.label << "\n";
for (auto &a : v.attributes)
{
ss << a.first << " | " << a.second << "\n";
}
}
return ss.str();
}
void cVertex::parseVertexLine(const std::string &line)
{
cVertex v;
v.userIndex = atoi(line.c_str());
int p = line.find("label");
int q = line.find("\"", p + 7);
v.label = line.substr(p + 7, q - p - 7);
while (1)
{
std::pair< std::string,std::string > ap;
p = line.find("=", q);
if (p == -1)
break;
ap.first = line.substr(q + 2, p - q - 2);
p = line.find("\"", p);
q = line.find("\"", p + 1);
ap.second = line.substr(p + 1, q - p - 1);
v.attributes.insert(ap);
int dbg = 0;
}
myVertex.push_back(v);
}
main()
{
cVertex theVertex;
theVertex.read("data.txt");
std::cout << theVertex.textVertexList();
return 0;
}
Related
The following figure shows bi-directional graph. I have represented following graph using boost-graph.
I have iterated from v1 --> v2 and v1 --> v3 but I am not able to visit from v3 --> v4. How to do that ?
Here is my code:
(vertex = V1) and
(graph = boost graph )
//Finding out edges of vertex
boost::graph_traits<BGType>::out_edge_iterator ei, ei_end;
boost::tie(ei, ei_end) = out_edges( vertex, graph );
for( boost::tie(ei, ei_end) = out_edges(vertex, graph); ei != ei_end; ++ei)
{
auto target = boost::target ( *ei, graph );
graph[target]._isVisible = false;
}
//Finding in edges of vertex
boost::graph_traits<BGType>::in_edge_iterator ein, ein_end;
boost::tie(ein, ein_end) = in_edges( vertex, graph );
for( boost::tie(ein, ein_end) = in_edges(vertex, graph); ein != ein_end; ++ein)
{
auto source = boost::source ( *ein, graph );
graph[source]._isVisible = false;
}
It helps if you get your terminology straight. L1, L2, L3 are edges not vertices.
So, you want to hide all edges and additionally all vertices with degree 0.
You could instead give edges a visibility flag:
struct EdgeProps {
bool _isVisible = true;
};
using BGType = boost::adjacency_list< //
boost::vecS, //
boost::vecS, //
boost::bidirectionalS, //
VertexProps, //
EdgeProps>;
Now I'd make that function:
void hide_connections(Name name, BGType& graph) {
auto it = graph.named_vertices.find(name);
assert(it != graph.named_vertices.end());
using boost::make_iterator_range;
for (auto e : make_iterator_range(out_edges(*it, graph)))
graph[e]._isVisible = false;
for (auto e : make_iterator_range(in_edges(*it, graph)))
graph[e]._isVisible = false;
}
Now, the visibility of vertices is a resultant - a derived property. You could calculate it on the fly:
auto visible = [&graph](V v) {
for (auto e : make_iterator_range(out_edges(v, graph)))
if (graph[e]._isVisible)
return true;
for (auto e : make_iterator_range(in_edges(v, graph)))
if (graph[e]._isVisible)
return true;
return false;
};
Indeed, this satisfies your requirements: Live On Compiler Explorer
#include <boost/graph/adjacency_list.hpp>
#include <boost/property_map/function_property_map.hpp>
#include <iostream>
using Name = std::string;
struct VertexProps {
VertexProps(Name n = "unnamed") : name(std::move(n)) {}
Name name;
};
struct EdgeProps {
bool _isVisible = true;
};
template <> struct boost::graph::internal_vertex_constructor<VertexProps> {
using type = boost::graph::vertex_from_name<VertexProps>;
};
template <> struct boost::graph::internal_vertex_name<VertexProps> {
struct type {
using result_type = Name;
Name const& operator()(VertexProps const& vp) const { return vp.name; }
Name& operator()(VertexProps& vp) const { return vp.name; }
};
};
using BGType = boost::adjacency_list< //
boost::vecS, //
boost::vecS, //
boost::bidirectionalS, //
VertexProps, //
EdgeProps>;
using V = BGType::vertex_descriptor;
using E = BGType::edge_descriptor;
void hide(Name name, BGType& graph) {
auto it = graph.named_vertices.find(name);
assert(it != graph.named_vertices.end());
for (auto e : make_iterator_range(out_edges(*it, graph)))
graph[e]._isVisible = false;
for (auto e : make_iterator_range(in_edges(*it, graph)))
graph[e]._isVisible = false;
}
int main() {
BGType graph;
for (auto [from, to] : {
std::pair{"V1", "V2"},
{"V1", "V3"},
{"V3", "V4"},
})
{
add_edge(from, to, graph);
add_edge(to, from, graph);
}
auto visible = [&graph](V v) { return 0 != degree(v, graph); };
auto names = get(&VertexProps::name, graph);
auto print = [&](std::ostream& os = std::cout) {
for (auto v : boost::make_iterator_range(vertices(graph))) {
if (!visible(v))
continue;
os << names[v] << " -->";
for (auto e : make_iterator_range(out_edges(v, graph))) {
if (graph[e]._isVisible)
os << " " << names[target(e, graph)];
}
os << "\n";
}
};
print();
hide("V1", graph);
print(std::cout << "\nConnections of V1 hidden:\n");
}
Prints
V2 --> V1
V1 --> V2 V3
V3 --> V1 V4
V4 --> V3
Connections of V1 hidden:
V3 --> V4
V4 --> V3
Outside The Box
This is clumsy and inefficient:
auto visible = [&graph](V v) {
for (auto e : make_iterator_range(out_edges(v, graph)))
if (graph[e]._isVisible)
return true;
for (auto e : make_iterator_range(in_edges(v, graph)))
if (graph[e]._isVisible)
return true;
return false;
};
What you really want to be able to say is:
auto visible = [&graph](V v) { return 0 != degree(v, graph); };
However, it won't work because you don't actually delete anything, so BGL will think the edges are still there.
You can fix the model by using a filtered_graph_adaptor where you store the filterables OUTSIDE the graph model.
Filtered Graph
So, this shifts the perspective back to your original: hiding vertices. Let's start out simple:
std::set<V> vhidden;
std::set<E> ehidden;
This is the set that will contain all hidden vertex descriptors.
Now we can setup filter predicates and the adapted graph:
std::function epred = [&](E e) { return not ehidden.contains(e); };
std::function vpred = [&](V v) { return not vhidden.contains(v); };
boost::filtered_graph f(graph, epred, vpred);
Adding some helpers to hide edges/vertices.
auto ehide = [&](E e) {
if (auto u = target(e, graph); 0 == degree(u, f))
vhidden.insert(u);
ehidden.insert(e);
};
Notice that we're being lazy and using degree(v, f) on the filtered graph (!) so that we don't have to manually count the number of edges that were already filtered.
auto vhide = [&](Name const& name) {
auto it = graph.named_vertices.find(name);
assert(it != graph.named_vertices.end());
V v = *it;
vhidden.insert(v);
for (auto e: make_iterator_range(out_edges(v, graph)))
ehide(e);
for (auto e: make_iterator_range(in_edges(v, graph)))
ehide(e);
};
Hiding a vertex traverses exactly one level to neighbours unconditionally. That's good enough as a stopping condition (in response to your comment) because the degree cannot change unless by hiding more edges to the same target node.
Using the filtered view:
std::cout << "Filtered edges(" << ehidden.size() << "), vertices(" << vhidden.size() << ")\n";
print_graph(f, names, std::cout << "Filtered view: \n");
vhide("V1");
std::cout << "Filtered edges(" << ehidden.size() << "), vertices(" << vhidden.size() << ")\n";
print_graph(f, names, std::cout << "Connections of V1 hidden:\n");
See it Live On Compiler Explorer
Filtered edges(0), vertices(0)
Filtered view:
V2 --> V1
V1 --> V2 V3
V3 --> V1 V4
V4 --> V3
Filtered edges(4), vertices(2)
Connections of V1 hidden:
V3 --> V4
V4 --> V3
Full Listing (Filtered Graph)
For posterity: Live On Compiler Explorer
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/property_map/function_property_map.hpp>
#include <iostream>
using Name = std::string;
struct VertexProps {
VertexProps(Name n = "unnamed") : name(std::move(n)) {}
Name name;
};
struct EdgeProps {
bool _isVisible = true;
};
template <> struct boost::graph::internal_vertex_constructor<VertexProps> {
using type = boost::graph::vertex_from_name<VertexProps>;
};
template <> struct boost::graph::internal_vertex_name<VertexProps> {
struct type {
using result_type = Name;
Name const& operator()(VertexProps const& vp) const { return vp.name; }
Name& operator()(VertexProps& vp) const { return vp.name; }
};
};
using BGType = boost::adjacency_list< //
boost::vecS, //
boost::vecS, //
boost::bidirectionalS, //
VertexProps, //
EdgeProps>;
using V = BGType::vertex_descriptor;
using E = BGType::edge_descriptor;
int main() {
BGType graph;
for (auto [from, to] : {
std::pair{"V1", "V2"},
{"V1", "V3"},
{"V3", "V4"},
})
{
add_edge(from, to, graph);
add_edge(to, from, graph);
}
auto names = get(&VertexProps::name, graph);
print_graph(graph, names, std::cout << "Unfiltered graph:\n");
std::set<V> vhidden;
std::set<E> ehidden;
std::function epred = [&](E e) { return not ehidden.contains(e); };
std::function vpred = [&](V v) { return not vhidden.contains(v); };
boost::filtered_graph f(graph, epred, vpred);
auto ehide = [&](E e) {
if (auto u = target(e, graph); 0 == degree(u, f))
vhidden.insert(u);
ehidden.insert(e);
};
auto vhide = [&](Name const& name) {
auto it = graph.named_vertices.find(name);
assert(it != graph.named_vertices.end());
V v = *it;
vhidden.insert(v);
for (auto e: make_iterator_range(out_edges(v, graph)))
ehide(e);
for (auto e: make_iterator_range(in_edges(v, graph)))
ehide(e);
};
std::cout << "Filtered edges(" << ehidden.size() << "), vertices(" << vhidden.size() << ")\n";
print_graph(f, names, std::cout << "Filtered view: \n");
vhide("V1");
std::cout << "Filtered edges(" << ehidden.size() << "), vertices(" << vhidden.size() << ")\n";
print_graph(f, names, std::cout << "Connections of V1 hidden:\n");
}
I'm new to boost::graph (and boost really). I want to use boost::filtered_graph many times on the same original graph, and use the write_graphviz function to let me visualise the results. I think my understanding must be off though because the following code isn't doing what I think it should: to output the same graph with print_graph and write_graphviz.
MWE (compiled with C++14, gcc 9.3 on Ubuntu 20.04; boost version 1.73):
#include <cstdio>
#include <fstream>
#include <iostream>
#include <boost/graph/copy.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/graph/graphviz.hpp>
using namespace std;
typedef boost::adjacency_list< boost::vecS, boost::vecS > Graph;
typedef boost::graph_traits<Graph>::vertex_descriptor vertex_descriptor;
typedef boost::graph_traits<Graph>::vertex_iterator vertex_iterator;
template <typename GraphType>
struct uniform_random_vertex_filter
{
uniform_random_vertex_filter() : prob(1.0) {} // default constructor is required
uniform_random_vertex_filter(float p) : prob(p) {}
bool operator()(const typename boost::graph_traits<GraphType>::vertex_descriptor& v) const
{
return drand48() < prob; // randomly select some vertices
}
private:
float prob;
};
int main(int argn, char **argc) {
unsigned int n = 5;
ofstream of;
Graph g(n);
vertex_iterator vit, uit, vend;
// build a complete graph on n vertices (I'm sure there's a simpler command for this):
for (boost::tie(vit,vend) = vertices(g); vit != vend; ++vit) {
for (uit = vit; uit != vend; ++uit) {
if (uit == vit) { continue; }
add_edge(*vit, *uit, g);
}
}
std::cout << "Original graph (OriginalGraph.dot):" << std::endl;
boost::print_graph(g);
of.open("OriginalGraph.dot", std::ios::trunc);
boost::write_graphviz(of, g);
of.close();
uniform_random_vertex_filter<Graph> vfilter(0.5);
boost::filtered_graph<Graph, boost::keep_all, uniform_random_vertex_filter<Graph> >
filteredGraph(g, boost::keep_all(), vfilter);
std::cout << "Filtered graph -- random selection of vertices (RandomVertexSelection.dot):" << std::endl;
boost::print_graph(filteredGraph);
Graph F;
boost::copy_graph(filteredGraph,F);
of.open("RandomVertexSelection.dot", std::ios::trunc);
boost::write_graphviz(of, F);
of.close();
return 0;
}
Which produces this output:
> Debug/BoostGraphFilter
Original graph:
0 --> 1 2 3 4
1 --> 2 3 4
2 --> 3 4
3 --> 4
4 -->
Filtered graph -- random selection of vertices (RandomVertexSelection.dot):
0 --> 1 2 3 4
1 --> 2 3
2 --> 3
>
--- which is fine, but the dot files are:
> cat OriginalGraph.dot
digraph G {
0;
1;
2;
3;
4;
0->1 ;
0->2 ;
0->3 ;
0->4 ;
1->2 ;
1->3 ;
1->4 ;
2->3 ;
2->4 ;
3->4 ;
}
> cat RandomVertexSelection.dot
digraph G {
0;
1;
2;
}
Hence the filtered_graph that's printed isn't the same as that written to .dot file (which has lost all the edges in this case).
Can someone please help me understand what I've done wrong?
Your filter is random. And since you didn't retain any state to make it transparent or deterministic, the results are random. Simple as that.
Ironically, at the same time you managed to get completely deterministic results across runs because you fail to use random correctly (e.g. seeding the generator).
In your case, the simplest would be to copy before first use: Live On Coliru
using Graph = boost::adjacency_list<boost::vecS, boost::vecS>;
Graph make_complete_graph(size_t n);
void report(Graph const& g, std::string name);
struct uniform_random_vertex_filter {
float prob = 1.0f;
bool operator()(auto v) const {
return drand48() < prob;
}
};
int main() {
Graph g = make_complete_graph(5);
report(g, "OriginalGraph");
for (int pct = 30; pct < 100; pct+=10) {
Graph F;
boost::copy_graph(boost::filtered_graph( //
g, //
boost::keep_all{},
uniform_random_vertex_filter{pct / 100.0f}),
F);
report(F, "RandomVertexSelection_" + std::to_string(pct));
}
}
// build a complete graph on n vertices
Graph make_complete_graph(size_t n)
{
Graph g(n);
for (auto [vit, vend] = vertices(g); vit != vend; ++vit) {
for (auto uit = vit; uit != vend; ++uit) {
if (uit != vit)
add_edge(*vit, *uit, g);
}
}
return g;
}
void report(Graph const& g, std::string name) {
boost::print_graph(g, std::cout << name << ":");
std::ofstream of(name + ".dot");
boost::write_graphviz(of, g);
}
If you want a stable random "cut" of a graph, make the filter stateful. This could be handy e.g. if you have a graph too large to copy.
Fixing The Random
As a sidenote, fixing the random to actually be seeded and reliably uniform:
Live On Coliru
#include <boost/graph/copy.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/graph/graphviz.hpp>
#include <fstream>
#include <iostream>
#include <random>
using Graph = boost::adjacency_list<boost::vecS, boost::vecS>;
using Filter = std::function<bool(Graph::vertex_descriptor)>;
Graph make_complete_graph(size_t n);
void report(Graph const& g, std::string name);
int main() {
Graph g = make_complete_graph(5);
report(g, "OriginalGraph");
std::mt19937 urbg{std::random_device{}()};
for (int pct = 30; pct < 100; pct += 10) {
Graph F;
std::bernoulli_distribution dist(pct / 100.);
boost::copy_graph(
boost::filtered_graph(g, boost::keep_all{},
Filter([&](auto) { return dist(urbg); })),
F);
report(F, "RandomVertexSelection_" + std::to_string(pct));
}
}
// build a complete graph on n vertices
Graph make_complete_graph(size_t n)
{
Graph g(n);
for (auto [vit, vend] = vertices(g); vit != vend; ++vit) {
for (auto uit = vit; uit != vend; ++uit) {
if (uit != vit)
add_edge(*vit, *uit, g);
}
}
return g;
}
void report(Graph const& g, std::string name) {
boost::print_graph(g, std::cout << name << ":");
std::ofstream of(name + ".dot");
boost::write_graphviz(of, g);
}
Now prints different random cuts every run.
I'm using the Boost graph library exploiting the following definitions
template <typename Vertex>
using graph = boost::adjacency_list<boost::listS,
boost::vecS,
boost::bidirectionalS,
Vertex>
template <typename Vertex>
using vertex = typename graph<Vertex>::vertex_descriptor;
where the template parameter Vertex is employed to use the BGL with bundled properties.
I'm not that confident with boost at the moment and I'd like to understand if is there a way to get the begin() and end() iterators of the "bundled property list".
In other words, let's assume I have this struct
struct City
{
string name;
int population;
vector<int> zipcodes;
};
and I want to use it as template parameter for my graph. I know I can iterate over vertexes like this
(assuming g is my adjacency list)
for(auto vd : boost::make_iterator_range(vertices(g))
{
auto v = g[*vd];
// do something with v
}
yet I'd like to do this
for(auto & v : some_iterator)
// v is a city ...
is there any way to do this ?
I've adapted my code from your now-deleted question to the more specific example. By now you already realize you are NOT in fact iterating vertices, but their bundles.
I suggest you use a transforming/indirected iterator adaptor. The easiest way I can think of is to use Boost Range Adaptors, specifically using transformed:
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/range/adaptors.hpp>
using boost::adaptors::transformed;
template <typename Vertex> struct Wrapper {
using graph = boost::adjacency_list<boost::listS, boost::vecS,
boost::bidirectionalS, Vertex>;
using vertex = typename graph::vertex_descriptor;
auto bundles() {
auto accessor = [map = get(boost::vertex_bundle, _g)](vertex v) -> auto& {
return map[v];
};
return vertices(_g) | transformed(accessor);
}
auto bundles() const {
auto accessor = [map = get(boost::vertex_bundle, _g)](vertex v) -> auto& {
return map[v];
};
return vertices(_g) | transformed(accessor);
}
private:
graph _g {5};
};
struct City {
std::string name;
int population;
std::vector<int> zipcodes;
};
static int id_gen = 0;
struct Sample2 { int id = ++id_gen; };
#include <iostream>
#include <iomanip>
int main() {
Wrapper<City> demo;
for (City& c : demo.bundles()) {
c.name = "name " + std::to_string(rand() % 100);
c.population = rand() % 128 * 190'000;
c.zipcodes = {1234, 2345};
}
auto const& const_ref = demo;
for (City const& c : const_ref.bundles()) {
std::cout << std::quoted(c.name) << ", population " << c.population << "\n";
}
}
Prints
"name 83", population 13300000
"name 77", population 21850000
"name 93", population 24130000
"name 86", population 20520000
"name 49", population 14630000
I'm successfully using boost graph's component finder to assign color, i.e. the component's index to every vertex in my graph like so:
#include <boost/graph/connected_components.hpp>
#include <boost/graph/adjacency_list.hpp>
boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS> g;
std::vector<int> compon_map(boost::num_vertices(g));
int number_of_components = boost::connected_components(g, &compon_map[0]);
This will then yield a different number_of_components after each iteration in my simulation (not shown) because I do
boost::clear_vertex(v, g);
in between to erase some edges based on some condition.
The thing is that in my simulation I want to write out some property (say weight) of all edges, and the length of the edge iterator needs to stay constant (dataset restrictions).
My question is therefore: Is there a way to pass some edge property like a
int L = boost::num_edges(g);
std::vector<bool> is_still_existent(L); // or
std::vector<double> edge_weights(L);
to the boost::connected_components (that then counts edges only based on that property) or is there another way to trick the edge iterator to stay at the initial length even after having removed edges?
Thanks in advance for any hints :)
Yes. You can use a filtered graph adaptor with an edge filter. I have several answers up on SO showing how to use it, but will see whether I can usefully create an example based on on your snippet.
So I made a self-contained sample¹: Live On Coliru,
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/connected_components.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <boost/graph/random.hpp>
#include <boost/graph/graphviz.hpp>
#include <random>
using G = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS>;
using V = G::vertex_descriptor;
using E = G::edge_descriptor;
using W = double;
int main(int argc, char** argv) {
G g;
auto seed = 9353; // fixed seed for demo
if (argc > 1) {
seed = atol(argv[1]);
std::cerr << "Using PRNG seed: " << seed << "\n";
}
std::mt19937 engine(seed);
auto weight_gen = bind(std::uniform_real_distribution<W>(0, 1), engine);
boost::generate_random_graph(g, 10, 6, engine);
std::map<E, W> weights;
for (auto e : boost::make_iterator_range(edges(g)))
weights[e] = weight_gen();
std::vector<int> components(boost::num_vertices(g));
auto cmap = boost::make_iterator_vertex_map(components.data());
int n = boost::connected_components(g, cmap);
std::cerr << n << " components\n";
boost::dynamic_properties dp;
dp.property("node_id", get(boost::vertex_index, g));
dp.property("style", boost::make_constant_property<V>(std::string("filled")));
dp.property("color",
boost::make_transform_value_property_map(
[](int componentid) {
static std::array cc{"red", "green", "yellow",
"blue", "brown", "black",
"orange", "purple"};
return cc[componentid % cc.size()];
},
cmap));
dp.property("label", boost::make_assoc_property_map(weights));
boost::write_graphviz_dp(std::cout, g, dp);
}
which generates a pseudo-random graph:
Let's add some filtering to it:
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/connected_components.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <boost/property_map/function_property_map.hpp>
#include <boost/graph/random.hpp>
#include <boost/graph/graphviz.hpp>
#include <random>
using G = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS>;
using V = G::vertex_descriptor;
using E = G::edge_descriptor;
using W = double;
struct NonRemoved {
std::set<E> const* _ref;
bool operator()(E e) const { return not _ref->contains(e); }
};
int main(int argc, char** argv)
{
G g;
auto seed = 9353; // fixed seed for demo
if (argc > 1) {
seed = atol(argv[1]);
std::cerr << "Using PRNG seed: " << seed << "\n";
}
std::mt19937 engine(seed);
auto weight_gen = bind(std::uniform_real_distribution<W>(0, 1), engine);
boost::generate_random_graph(g, 10, 6, engine);
std::map<E, W> weights;
for (auto e : boost::make_iterator_range(edges(g)))
weights[e] = weight_gen();
std::vector<int> components(boost::num_vertices(g));
auto cmap = boost::make_iterator_vertex_map(components.data());
auto random_edges = [&g] {
auto [f,l] = edges(g);
std::deque<E> re(f,l);
std::random_shuffle(begin(re), end(re));
return re;
}();
std::set<E> removed;
NonRemoved predicate{&removed};
boost::filtered_graph<G, NonRemoved, boost::keep_all> f(g, predicate, {});
do {
int n = boost::connected_components(f, cmap);
std::cerr << n << " components\n";
boost::dynamic_properties dp;
dp.property("node_id", get(boost::vertex_index, f));
dp.property("style", boost::make_constant_property<V>(std::string("filled")));
dp.property("color",
boost::make_transform_value_property_map(
[](int componentid) {
static std::array cc{"red", "green", "yellow",
"blue", "brown", "black",
"orange", "purple"};
return cc[componentid % cc.size()];
},
cmap));
dp.property("color",
boost::make_function_property_map<E>([&removed](E e) {
return removed.contains(e) ? "red" : "blue";
}));
dp.property("label",
boost::make_function_property_map<E>([&removed, &weights](E e) {
if (removed.contains(e))
return std::string("REMOVED");
return std::to_string(weights.at(e));
}));
std::ofstream ofs("graph_" + std::to_string(random_edges.size()) + ".dot");
boost::write_graphviz_dp(ofs, f, dp);
removed.insert(random_edges.front());
random_edges.pop_front();
} while (not random_edges.empty());
}
Now writes a series of graph_XXX.dot graphs that display as:
¹ (changing the vertex container selector to vecS for simplicity)
The main question:
I am able to create a graph implementation with information structs assigned to the vertices and edges:
struct vertex_info {std::string name;};
struct edge_info {std::string name;};
typedef boost::adjacency_list<
boost::vecS,
boost::vecS,
boost::undirectedS,
vertex_info,
edge_info> UndirectedGraph;
And for an instance of UndirectedGraph, g, I can easily iterate over the vertices, and access their information:
for(size_t i=0; i<boost::num_vertices(g); i++){
std::cout << g[i].name << std::endl;
}
but I am unable to figure out how to do the same for the edges. I have come across some iterators to loop over all the edges, but I cannot access these edges as some kind of object or something with properties. How can I access the edge information of g?
A minimal working demonstration:
#include <iostream>
#include <utility>
#include <vector>
#include <string>
#include "boost/graph/graph_traits.hpp"
#include "boost/graph/adjacency_list.hpp"
int main(int argc, char *argv[])
{
//Add vertex information struct
struct vertex_info {
std::string name;
};
//Add edge information struct
struct edge_info {
std::string name;
};
//Typedef my graph implimentation
typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, vertex_info, edge_info> UndirectedGraph;
//Our set of edges, and count N: (0-7) and 8
enum {C, D, G, I, S, J, L, H, N};
const char *name = "CDGISJLH";
//Create a vector of edges
typedef std::pair<int, int> Edge;
std::vector<Edge> edgeVec;
edgeVec.push_back(Edge(C,D));
edgeVec.push_back(Edge(D,G));
edgeVec.push_back(Edge(I,G));
edgeVec.push_back(Edge(G,L));
edgeVec.push_back(Edge(H,G));
edgeVec.push_back(Edge(I,S));
edgeVec.push_back(Edge(S,J));
edgeVec.push_back(Edge(L,J));
edgeVec.push_back(Edge(H,J));
//Now we can initialize our graph using iterators from our above vector
UndirectedGraph g(edgeVec.begin(), edgeVec.end(), N);
std::cout << num_edges(g) << "\n"; //Outputs: 9
//loop over vertices, access "name" property
for(size_t i=0; i<boost::num_vertices(g); i++){
//And add information to the edges
g[i].name = "foo";
}
//We can access the first vertice and print the property
std::cout << g[0].name << std::endl; //Outputs: foo
//Edge iterator for or graph
typedef boost::graph_traits<UndirectedGraph>::edge_iterator edge_iterator;
//Iterate through all the edges
std::pair<edge_iterator, edge_iterator> ei = boost::edges(g);
for(edge_iterator edge_iter = ei.first; edge_iter != ei.second; ++edge_iter) {
//How can I access the edge property???
}
}
I have figure out the problem by walking through this example: https://www.boost.org/doc/libs/1_71_0/libs/graph/doc/bundles.html
The fix:
Although I still don't exactly understand how it all works. It seems like you have to use edge_iter as some kind of index into g:
//Edge iterator for or graph
typedef boost::graph_traits<MRFGraph>::edge_iterator edge_iterator;
//Iterate through all the edges
std::pair<edge_iterator, edge_iterator> ei = boost::edges(g);
for(edge_iterator edge_iter = ei.first; edge_iter != ei.second; ++edge_iter) {
g[*edge_iter].name = "bar";
std::cout << *edge_iter << ": " << g[*edge_iter].name << std::endl;
}
Output:
If I add this to the minimal working demonstration, it produces the following output:
9
foo
(0,1): bar
(1,2): bar
(3,2): bar
(2,6): bar
(7,2): bar
(3,4): bar
(4,5): bar
(6,5): bar
(7,5): bar
May not be exactly what you are looking for but does achieve what you are after
#include <iostream>
#include <utility>
#include <vector>
#include <string>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
namespace boost {
enum edge_myname_t { edge_myname };
BOOST_INSTALL_PROPERTY(boost::edge, myname);
}
int main(int argc, char* argv[]) {
// Add vertex information struct
struct vertex_info {
std::string name;
};
// Add edge information struct
//struct edge_info {
//std::string name;
//};
using EdgeName = boost::property<boost::edge_myname_t, std::string>;
// Typedef my graph implimentation
using UndirectedGraph =
boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS,
vertex_info, EdgeName>;
// Our set of edges, and count N: (0-7) and 8
enum { C, D, G, I, S, J, L, H, N };
const char* name = "CDGISJLH";
// Create a vector of edges
//using Edge = std::pair<int, int>;
//std::vector<Edge> edgeVec;
//edgeVec.push_back(Edge(C, D));
//edgeVec.push_back(Edge(D, G));
//edgeVec.push_back(Edge(I, G));
//edgeVec.push_back(Edge(G, L));
//edgeVec.push_back(Edge(H, G));
//edgeVec.push_back(Edge(I, S));
//edgeVec.push_back(Edge(S, J));
//edgeVec.push_back(Edge(L, J));
//edgeVec.push_back(Edge(H, J));
// Now we can initialize our graph using iterators from our above vector
UndirectedGraph g(N);
//UndirectedGraph g(edgeVec.begin(), edgeVec.end(), N);
boost::add_edge(C, D, EdgeName("#1"), g);
boost::add_edge(D, G, EdgeName("#2"), g);
boost::add_edge(I, G, EdgeName("#3"), g);
boost::add_edge(G, L, EdgeName("#4"), g);
boost::add_edge(H, G, EdgeName("#5"), g);
boost::add_edge(I, S, EdgeName("#6"), g);
boost::add_edge(S, J, EdgeName("#7"), g);
boost::add_edge(L, J, EdgeName("#8"), g);
boost::add_edge(H, J, EdgeName("#9"), g);
boost::property_map<UndirectedGraph, boost::edge_myname_t>::type get_name =
boost::get(boost::edge_myname, g);
std::cout << num_edges(g) << "\n"; // Outputs: 9
// loop over vertices, access "name" property
for (size_t i = 0; i < boost::num_vertices(g); i++) {
// And add information to the edges
g[i].name = "foo";
}
// We can access the first vertice and print the property
std::cout << g[0].name << std::endl; // Outputs: foo
// Edge iterator for or graph
using EdgeIterator = boost::graph_traits<UndirectedGraph>::edge_iterator;
// Iterate through all the edges
std::pair<EdgeIterator, EdgeIterator> ei = boost::edges(g);
for (EdgeIterator edge_iter = ei.first; edge_iter != ei.second; ++edge_iter) {
// How can I access the edge property???
std::cout << get_name[*edge_iter] << "\n";
}
}
I just slightly modified some of the code for my own readability issues.
For reference, check this out.