I'm learning about BGL and pieced together a program (from various related StackOverflow answers) to build a graph that should be able to deal with polymorphic vertices and edges. The graph should be able to handle parallel edges (of the same type or not). I'd like to get dijkstra_shortest_paths from a starting vertex_descriptor to : 1. all Park's; 2. all Park's and City's; 3. all City's via Railway only. Is that even possible ? Thank you.
#include <iostream>
#include <deque>
#include "boost/graph/adjacency_list.hpp"
#include "boost/graph/topological_sort.hpp"
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/variant.hpp>
#include <boost/graph/adj_list_serialize.hpp>
#include <boost/property_map/function_property_map.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
using namespace boost;
struct Highway {
std::string name;
int miles;
int speed_limit;
int lanes;
bool divided;
};
struct Railway {
std::string name;
int km;
};
struct City {
std::string name;
int population;
std::vector<int> zipcodes;
};
struct Park {
std::string name;
int population;
std::vector<int> zipcodes;
};
struct Country {
std::string name;
bool use_right; // Drive on the left or right
bool use_metric; // mph or km/h
};
int main(int argc, char *argv[]) {
typedef boost::adjacency_list<
boost::listS, boost::vecS, boost::bidirectionalS,
boost::variant<City, Park>, boost::variant<Highway, Railway>, Country>
Map;
City c1 = {"San Jose", 1200000, {95126, 95128}};
City c2 = {"San Francisco", 500000, {95008, 95009}};
City c3 = {"Santa Cruz", 300000, {94001, 94002}};
City c4 = {"Oakland", 800000, {93001, 93002}};
City c5 = {"San Diego", 2800000, {92001, 92002}};
Map map; // load the map
Map::vertex_descriptor c1d = boost::add_vertex(c1, map),
c2d = boost::add_vertex(c2, map),
c3d = boost::add_vertex(c3, map),
c4d = boost::add_vertex(c4, map),
c5d = boost::add_vertex(c5, map);
add_edge(c1d, c2d, Highway{"101", 42, 65, 3, true}, map);
add_edge(c2d, c3d, Highway{"280", 52, 60, 1, true}, map);
add_edge(c2d, c3d, Highway{"92", 13, 60, 1, true}, map);
//add_edge(c2, c3, map);
//add_edge(c1, c3, map);
map[graph_bundle].name = "United States";
map[graph_bundle].use_right = true;
map[graph_bundle].use_metric = false;
using vertex_descriptor = boost::graph_traits<Map>::vertex_descriptor;
Map::vertex_descriptor from = *vertices(map).first;
std::vector<int> distances(num_vertices(map));
auto v_index = get(boost::vertex_index, map);
auto v_bundle = get(boost::vertex_bundle, map);
dijkstra_shortest_paths(map, from,
weight_map(get(get(&Highway::miles, map))
.distance_map(make_iterator_property_map(distances.begin(),
get(vertex_index, map))));
std::cout << "distances and parents:" << std::endl;
graph_traits <Map>::vertex_iterator vi, vend;
for (boost::tie(vi, vend) = vertices(map); vi != vend; ++vi) {
std::cout << "distance(" << get<City>(&City::name, map[*vi]) << ") = " << distances[*vi] << ", " << "\n";
}
std::cout << std::endl;
return 0;
}
So, in short, you want to access a property from a variant element type. You could make your own property map, facilitate your own set of get/put accessors or use a property map adaptor.
The latter being least work:
auto highway_miles = boost::make_transform_value_property_map(
[](Interconnect const& conn) {
if (auto* c = boost::get<Highway>(&conn)) {
return c->miles;
} else {
return std::numeric_limits<Distance>::max();
}
},
get(boost::edge_bundle, map));
dijkstra_shortest_paths(
map, from,
weight_map(highway_miles)
.distance_map(make_iterator_property_map(
distances.begin(), v_index)));
In fact with vecS chosen vertex container selector, you could write it more succinctly:
dijkstra_shortest_paths(
map, from, weight_map(highway_miles).distance_map(distances.data()));
Live Demo
Live on Compiler Explorer
#include <deque>
#include <iostream>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/topological_sort.hpp>
#include <boost/property_map/function_property_map.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <boost/variant.hpp>
using Distance = int;
struct Highway {
std::string name;
Distance miles;
unsigned speed_limit, lanes;
bool divided;
};
struct Railway {
std::string name;
Distance km;
};
struct City {
std::string name;
unsigned population;
std::vector<unsigned> zipcodes;
};
struct Park {
std::string name;
unsigned population;
std::vector<unsigned> zipcodes;
};
using Interconnect = boost::variant<Highway, Railway>;
using Area = boost::variant<City, Park>;
enum class RoadOrientation { right, left };
enum class UnitSystem { metric, imperial };
struct Country {
std::string name;
RoadOrientation driving;
UnitSystem measure;
};
using Map = boost::adjacency_list< //
boost::listS, boost::vecS, boost::bidirectionalS, //
Area, Interconnect, Country>;
int main()
{
Map map;
map[boost::graph_bundle] = {
"United States",
RoadOrientation::right,
UnitSystem::imperial,
};
auto c1d = add_vertex(City{"San Jose", 1200000, {95126, 95128}}, map);
auto c2d = add_vertex(City{"San Francisco", 500000, {95008, 95009}}, map);
auto c3d = add_vertex(City{"Santa Cruz", 300000, {94001, 94002}}, map);
add_vertex(City{"Oakland", 800000, {93001, 93002}}, map);
add_vertex(City{"San Diego", 2800000, {92001, 92002}}, map);
add_edge(c1d, c2d, Highway{"101", 42, 65, 3, true}, map);
add_edge(c2d, c3d, Highway{"280", 52, 60, 1, true}, map);
add_edge(c2d, c3d, Highway{"92", 13, 60, 1, true}, map);
using V = Map::vertex_descriptor;
V from = vertex(0, map);
std::vector<int> distances(num_vertices(map));
auto v_index = get(boost::vertex_index, map);
auto highway_miles = boost::make_transform_value_property_map(
[](Interconnect const& conn) {
if (auto* c = boost::get<Highway>(&conn)) {
return c->miles;
} else {
return std::numeric_limits<Distance>::max();
}
},
get(boost::edge_bundle, map));
dijkstra_shortest_paths(
map, from,
weight_map(highway_miles)
.distance_map(make_iterator_property_map(
distances.begin(), v_index)));
// dijkstra_shortest_paths(
// map, from, weight_map(highway_miles).distance_map(distances.data()));
std::cout << "distances and parents:" << std::endl;
auto name_of = [](auto const& area) {
return boost::apply_visitor(
[](auto const& c_or_p) { return c_or_p.name; }, area);
};
for (auto v : boost::make_iterator_range(vertices(map)))
std::cout << "distance(" << name_of(map[v]) << ") = " << distances[v] << "\n";
}
Prints
distances and parents:
distance(San Jose) = 0
distance(San Francisco) = 42
distance(Santa Cruz) = 55
distance(Oakland) = 2147483647
distance(San Diego) = 2147483647
More Ideas
I've provided recommendations/alternatives in the past:
Graph with two types of nodes
C++ and generic graph distance algorithm
UPDATE: Filtering Graph
In response to
I'd like to get dijkstra_shortest_paths from a starting vertex_descriptor to : 1. all Park's; 2. all Park's and City's; 3. all City's via Railway only. Is that even possible ? Thank you.
Here's how you might write that using filtered graphs:
using V = Map::vertex_descriptor;
using E = Map::edge_descriptor;
using VertexFilter = std::function<bool(V)>;
using EdgeFilter = std::function<bool(E)>;
using FMap = boost::filtered_graph<Map, EdgeFilter, VertexFilter>;
EdgeFilter any_interconnect = boost::keep_all{};
EdgeFilter railway_only = [&map](E e) -> bool { return get<Railway>(&map[e]); };
VertexFilter parks = [&map](V v) -> bool { return get<Park>(&map[v]); };
VertexFilter cities = [&map](V v) -> bool { return get<City>(&map[v]); };
VertexFilter parks_cities = boost::keep_all{};
struct {
FMap fmap;
std::string_view caption;
} cases[] = {
{FMap(map, any_interconnect, parks), "All parks"},
{FMap(map, any_interconnect, parks_cities), "All parks and cities"},
{FMap(map, railway_only, cities), "Cities by railway"},
};
for (auto const& [fmap, caption] : cases) {
std::cout << " ---- " << caption << "\n";
if (auto [vb, ve] = vertices(fmap); vb != ve) {
std::vector<Distance> distances(num_vertices(
fmap)); // or map, since descriptors are same domain
auto weight = make_transform_value_property_map(
distance_km, get(boost::edge_bundle, fmap));
dijkstra_shortest_paths(
fmap, *vb,
weight_map(weight).distance_map(distances.data()));
for (auto v : boost::make_iterator_range(vertices(fmap)))
std::cout << "distance(" << name_of(fmap[v])
<< ") = " << distances[v] << "km\n";
}
}
Here's a live demo:
Live On Compiler Explorer
#include <iostream>
#include <iomanip>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/adjacency_list_io.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/topological_sort.hpp>
#include <boost/graph/filtered_graph.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <boost/variant.hpp>
using Distance = double;
struct Highway {
std::string name;
Distance miles;
unsigned speed_limit, lanes;
bool divided;
};
struct Railway {
std::string name;
Distance km;
};
struct City {
std::string name;
unsigned population;
std::vector<unsigned> zipcodes;
};
struct Park {
std::string name;
unsigned population;
std::vector<unsigned> zipcodes;
};
using Interconnect = boost::variant<Highway, Railway>;
using Area = boost::variant<City, Park>;
enum class RoadOrientation { right, left };
enum class UnitSystem { metric, imperial };
struct {
Distance operator()(Highway const& hw) const { return 1.60934 * hw.miles; }
Distance operator()(Railway const& rw) const { return rw.km; }
Distance operator()(auto const& variant) const {
return boost::apply_visitor(
[this](auto const& obj) { return (*this)(obj); }, variant);
}
} static const inline distance_km;
struct {
std::string_view operator()(auto const& variant) const {
return boost::apply_visitor(
[](auto const& obj) -> std::string_view { return obj.name; },
variant);
}
} static const inline name_of;
struct Country {
std::string name;
RoadOrientation driving;
UnitSystem measure;
};
// for debug output
static inline std::ostream& operator<<(std::ostream& os, City const& c) { return os << "City{" << std::quoted(c.name) << ", pop:" << c.population << "}"; }
static inline std::ostream& operator<<(std::ostream& os, Park const& p) { return os << "Park{" << std::quoted(p.name) << ", pop:" << p.population << "}"; }
static inline std::ostream& operator<<(std::ostream& os, Highway const& hw) { return os << "Highway{" << std::quoted(hw.name) << ", " << distance_km(hw) << "km}"; }
static inline std::ostream& operator<<(std::ostream& os, Railway const& rw) { return os << "Railway{" << std::quoted(rw.name) << ", " << distance_km(rw) << "km}"; }
using Map = boost::adjacency_list< //
boost::listS, boost::vecS, boost::bidirectionalS, //
Area, Interconnect, Country>;
int main()
{
Map map;
map[boost::graph_bundle] = {
"United States",
RoadOrientation::right,
UnitSystem::imperial,
};
auto c1d = add_vertex(City{"San Jose", 1200000, {95126, 95128}}, map);
auto c2d = add_vertex(City{"San Francisco", 500000, {95008, 95009}}, map);
auto c3d = add_vertex(City{"Santa Cruz", 300000, {94001, 94002}}, map);
auto c4d = add_vertex(City{"Oakland", 800000, {93001, 93002}}, map);
add_vertex(City{"San Diego", 2800000, {92001, 92002}}, map);
auto p1d = add_vertex(Park{"Alum Rock Park", 0, {}}, map);
auto p2d = add_vertex(Park{"Calero County Park", 0, {}}, map);
auto p3d = add_vertex(Park{"Reinhardt Redwood", 0, {}}, map);
auto p4d = add_vertex(Park{"Knowland Park", 0, {}}, map);
add_edge(c1d, c2d, Highway{"101", 42, 65, 3, true}, map);
add_edge(c2d, c3d, Highway{"280", 52, 60, 1, true}, map);
add_edge(c2d, c3d, Highway{"92", 13, 60, 1, true}, map);
add_edge(c1d, p1d, Highway{"102", 27, 65, 2, false}, map);
add_edge(c1d, p2d, Railway{"N1", 36}, map);
add_edge(c4d, p3d, Highway{"703", 53, 65, 2, false}, map);
add_edge(c4d, p4d, Railway{"N2", 70}, map);
std::cout << " ---- map\n" << boost::write(map) << "\n";
using V = Map::vertex_descriptor;
using E = Map::edge_descriptor;
using VertexFilter = std::function<bool(V)>;
using EdgeFilter = std::function<bool(E)>;
using FMap = boost::filtered_graph<Map, EdgeFilter, VertexFilter>;
EdgeFilter any_interconnect = boost::keep_all{};
EdgeFilter railway_only = [&map](E e) -> bool { return get<Railway>(&map[e]); };
VertexFilter parks = [&map](V v) -> bool { return get<Park>(&map[v]); };
VertexFilter cities = [&map](V v) -> bool { return get<City>(&map[v]); };
VertexFilter parks_cities = boost::keep_all{};
struct {
FMap fmap;
std::string_view caption;
} cases[] = {
{FMap(map, any_interconnect, parks), "All parks"},
{FMap(map, any_interconnect, parks_cities), "All parks and cities"},
{FMap(map, railway_only, cities), "Cities by railway"},
};
for (auto const& [fmap, caption] : cases) {
std::cout << " ---- " << caption << "\n";
if (auto [vb, ve] = vertices(fmap); vb != ve) {
std::vector<Distance> distances(num_vertices(
fmap)); // or map, since descriptors are same domain
auto weight = make_transform_value_property_map(
distance_km, get(boost::edge_bundle, fmap));
dijkstra_shortest_paths(
fmap, *vb,
weight_map(weight).distance_map(distances.data()));
for (auto v : boost::make_iterator_range(vertices(fmap)))
std::cout << "distance(" << name_of(fmap[v])
<< ") = " << distances[v] << "km\n";
}
}
}
Prints
---- map
v
City{"San Jose", pop:1200000}
City{"San Francisco", pop:500000}
City{"Santa Cruz", pop:300000}
City{"Oakland", pop:800000}
City{"San Diego", pop:2800000}
Park{"Alum Rock Park", pop:0}
Park{"Calero County Park", pop:0}
Park{"Reinhardt Redwood", pop:0}
Park{"Knowland Park", pop:0}
e
0 1 Highway{"101", 67.5923km}
1 2 Highway{"280", 83.6857km}
1 2 Highway{"92", 20.9214km}
0 5 Highway{"102", 43.4522km}
0 6 Railway{"N1", 36km}
3 7 Highway{"703", 85.295km}
3 8 Railway{"N2", 70km}
---- All parks
distance(Alum Rock Park) = 0km
distance(Calero County Park) = 1.79769e+308km
distance(Reinhardt Redwood) = 1.79769e+308km
distance(Knowland Park) = 1.79769e+308km
---- All parks and cities
distance(San Jose) = 0km
distance(San Francisco) = 67.5923km
distance(Santa Cruz) = 88.5137km
distance(Oakland) = 1.79769e+308km
distance(San Diego) = 1.79769e+308km
distance(Alum Rock Park) = 43.4522km
distance(Calero County Park) = 36km
distance(Reinhardt Redwood) = 1.79769e+308km
distance(Knowland Park) = 1.79769e+308km
---- Cities by railway
distance(San Jose) = 0km
distance(San Francisco) = 1.79769e+308km
distance(Santa Cruz) = 1.79769e+308km
distance(Oakland) = 1.79769e+308km
distance(San Diego) = 1.79769e+308km
I made up some extra data to even have parks or railways.
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 trying to store the nested XML file:
<?xml version="1.0" encoding="utf-8"?>
<root>
<type>
<cars>
<car name="Garfield" weight="4Kg">
<spec serial_="e_54" source ="petrol" mileage="56"/>
<spec serial_="e_52" source="diesel" mileage="52"/>
<spec serial_="m_22" source="electric" mileage="51"/>
<additions source="steam" convert="153 0 1 0"/>
</car>
<car name="Awesome" weight="3Kg">
<spec serial_="t_54" source="petrol" mileage="16"/>
<spec serial_="t_52" source="wind" mileage="62"/>
<spec serial_="t_22" source="electric" mileage="81"/>
<additions source="water" convert="123 1 1 0"/>
</car>
</cars>
<planes>
<plane id="231" name ="abc">
<utilities serial_="e456" part="567"/>
</plane>
</type>
</root>
in the from of a boost library un-directed graph. As per the XML, I wish to make each "car" and "plane" a node while storing there attributes as node's data members. Next, their child nodes i.e. "spec", "additions" and "utilities" are to be stored in form of edges along with their attributes.
The code structure is as follow:
#include <boost/property_tree/xml_parser.hpp>
using boost::property_tree::ptree;
#include <iostream>
struct Car {
std::string name, weight;
struct Spec {
std::string serial_, source;
double mileage;
};
std::vector<Spec> specs;
};
//Note: struct identical to struct car have been devised to store planes and additions
static bool parse(ptree const& node, Car::Spec& into) {
into.serial_ = node.get<std::string>("<xmlattr>.serial_");
into.source = node.get<std::string>("<xmlattr>.source");
into.mileage = node.get<double>("<xmlattr>.mileage");
return true;
}
static bool parse(ptree const& node, Car& into) {
into.name = node.get<std::string>("<xmlattr>.name");
into.weight = node.get<std::string>("<xmlattr>.weight");
for (auto& [name, child] : node) {
if (name == "spec") {
into.specs.emplace_back();
if (!parse(child, into.specs.back())) {
return false;
}
}
}
return true;
}
static std::ostream& operator<<(std::ostream& os, Car const& car) {
os << "Name: " << car.name << ", Weight: " << car.weight;
for (auto& spec : car.specs) {
os << "\n -- [" << spec.serial_ << "; " << spec.source << "; "
<< spec.mileage << "]";
}
return os;
}
int main()
{
boost::property_tree::ptree pt;
{
std::ifstream ifs("input.xml");
read_xml(ifs, pt);
}
for (auto& [key, node] : pt.get_child("root.type.cars")) {
if ("car" == key) {
Car car;
parse(node, car);
std::cout << car << "\n";
}
}
}
I wish to get rid of the structs and have a class in-place instead for storage and form the depicted BGL graph.
All help is duly appreciated.
Thanks!!
Okay, because there's not a lot of other boost traffic, let's do this.
First, let's fix the input so that it is actually XML:
<?xml version="1.0" encoding="utf-8"?>
<root>
<type>
<cars>
<car name="Garfield" weight="4Kg">
<spec serial_="e_54" source ="petrol" mileage="56"/>
<spec serial_="e_52" source="diesel" mileage="52"/>
<spec serial_="m_22" source="electric" mileage="51"/>
<additions source="steam" convert="153 0 1 0"/>
</car>
<car name="Awesome" weight="3Kg">
<spec serial_="t_54" source="petrol" mileage="16"/>
<spec serial_="t_52" source="wind" mileage="62"/>
<spec serial_="t_22" source="electric" mileage="81"/>
<additions source="water" convert="123 1 1 0"/>
</car>
</cars>
<planes>
<plane id="231" name ="abc">
<utilities serial_="e456" part="567"/>
</plane>
</planes>
</type>
</root>
Now, let's add parsing for the planes:
struct Plane {
Id id;
std::string name;
struct Utilities {
std::string serial_, part;
};
Utilities utilities;
};
static bool parse(ptree const& node, Plane::Utilities& into) {
into.serial_ = node.get<std::string>("<xmlattr>.serial_");
into.part = node.get<std::string>("<xmlattr>.part");
return true;
}
static bool parse(ptree const& node, Plane& into) {
into.id = node.get<Id>("<xmlattr>.id");
into.name = node.get<std::string>("<xmlattr>.name");
if (auto child = node.get_child_optional("utilities")) {
return parse(*child, into.utilities);
}
return true;
}
So far, nothing new. Well, we might add the additions to cars:
struct Additions {
std::string source;
std::vector<double> convert;
};
Additions additions;
Which you can parse using something like
static bool parse(ptree const& node, Car::Additions& into) {
into.source = node.get<std::string>("<xmlattr>.source");
auto values = node.get<std::string>("<xmlattr>.convert");
if (!x3::parse(
values.begin(), values.end(),
x3::skip(x3::space) [*x3::double_],
into.convert))
return false;
return true;
}
Making It A Graph
Instead of "magically" not having the structs but still having the data (how?) you would probably want to attach the structs to your graph:
using VertexBundle = boost::variant<Car, Plane>;
using EdgeBundle = std::string;
using Graph = boost::adjacency_list<
boost::vecS, boost::vecS, boost::directedS,
VertexBundle, EdgeBundle>;
Vertices
There, now let's parse those vertices from the XML:
Graph g;
auto parse_vehicles = [&pt,&g]<typename Type>(auto path, auto key) {
for (auto& [k, node] : pt.get_child(path)) {
if (k == key) {
Type vehicle;
parse(node, vehicle);
add_vertex(vehicle, g);
}
}
};
parse_vehicles.operator()<Car>("root.type.cars", "car");
parse_vehicles.operator()<Plane>("root.type.planes", "plane");
Note how nice and generic that parse loop already was.
Edges
There's nothing in your question indicating how we get any edge information, so let's just make something up for demo purposes:
// TODO add edges, but there's no information on how to
add_edge(vertex(0, g), vertex(2, g), "This demo edge has no properties", g);
add_edge(vertex(2, g), vertex(1, g), "One more", g);
Now you can print the whole thing as before:
for (Vertex v : boost::make_iterator_range(vertices(g))) {
std::cout << g[v] << "\n";
}
Printing Live On Coliru
Name: Garfield, Weight: 4Kg
-- [e_54; petrol; 56]
-- [e_52; diesel; 52]
-- [m_22; electric; 51]
-- additions [steam; 153/0/1/0]
Name: Awesome, Weight: 3Kg
-- [t_54; petrol; 16]
-- [t_52; wind; 62]
-- [t_22; electric; 81]
-- additions [water; 123/1/1/0]
Id: 231, Name: abc
-- utilities [e456; 567]
As a bonus let's include a DOT graph output:
Full Live Demo
Live On Coliru
#include <boost/property_tree/xml_parser.hpp>
#include <boost/variant.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/spirit/home/x3.hpp>
namespace x3 = boost::spirit::x3;
using boost::property_tree::ptree;
#include <iostream>
using Id = std::uint32_t;
struct Car {
std::string name, weight;
struct Spec {
std::string serial_, source;
double mileage;
};
struct Additions {
std::string source;
std::vector<double> convert;
};
std::vector<Spec> specs;
Additions additions;
};
struct Plane {
Id id;
std::string name;
struct Utilities {
std::string serial_, part;
};
Utilities utilities;
};
static bool parse(ptree const& node, Car::Additions& into) {
into.source = node.get<std::string>("<xmlattr>.source");
auto values = node.get<std::string>("<xmlattr>.convert");
if (!x3::parse(
values.begin(), values.end(),
x3::skip(x3::space) [*x3::double_],
into.convert))
return false;
return true;
}
static bool parse(ptree const& node, Car::Spec& into) {
into.serial_ = node.get<std::string>("<xmlattr>.serial_");
into.source = node.get<std::string>("<xmlattr>.source");
into.mileage = node.get<double>("<xmlattr>.mileage");
into.mileage = node.get<double>("<xmlattr>.mileage");
return true;
}
static bool parse(ptree const& node, Car& into) {
into.name = node.get<std::string>("<xmlattr>.name");
into.weight = node.get<std::string>("<xmlattr>.weight");
for (auto& [name, child] : node) {
if (name == "spec") {
into.specs.emplace_back();
if (!parse(child, into.specs.back())) {
return false;
}
}
}
if (auto child = node.get_child_optional("additions")) {
return parse(*child, into.additions);
}
return true;
}
static bool parse(ptree const& node, Plane::Utilities& into) {
into.serial_ = node.get<std::string>("<xmlattr>.serial_");
into.part = node.get<std::string>("<xmlattr>.part");
return true;
}
static bool parse(ptree const& node, Plane& into) {
into.id = node.get<Id>("<xmlattr>.id");
into.name = node.get<std::string>("<xmlattr>.name");
if (auto child = node.get_child_optional("utilities")) {
return parse(*child, into.utilities);
}
return true;
}
static std::ostream& operator<<(std::ostream& os, Car const& car) {
os << "Name: " << car.name << ", Weight: " << car.weight;
for (auto& spec : car.specs) {
os << "\n -- [" << spec.serial_ << "; " << spec.source << "; "
<< spec.mileage << "]";
}
auto& a = car.additions;
if (!(a.source.empty() && a.convert.empty())) {
os << "\n -- additions [" << a.source << ";";
auto sep = ' ';
for (auto d : a.convert) {
os << std::exchange(sep, '/') << d;
}
os << "]";
}
return os;
}
static std::ostream& operator<<(std::ostream& os, Plane const& plane) {
os << "Id: " << plane.id << ", Name: " << plane.name;
auto& u = plane.utilities;
if (!(u.serial_.empty() && u.part.empty())) {
os << "\n -- utilities [" << u.serial_ << "; " << u.part << "]";
}
return os;
}
using VertexBundle = boost::variant<Car, Plane>;
using EdgeBundle = std::string;
using Graph = boost::adjacency_list<
boost::vecS, boost::vecS, boost::directedS,
VertexBundle, EdgeBundle>;
using Vertex = Graph::vertex_descriptor;
using Edge = Graph::edge_descriptor;
int main()
{
boost::property_tree::ptree pt;
{
std::ifstream ifs("input.xml");
read_xml(ifs, pt);
}
Graph g;
auto parse_vehicles = [&pt,&g]<typename Type>(auto path, auto key) {
for (auto& [k, node] : pt.get_child(path)) {
if (k == key) {
Type vehicle;
parse(node, vehicle);
add_vertex(vehicle, g);
}
}
};
parse_vehicles.operator()<Car>("root.type.cars", "car");
parse_vehicles.operator()<Plane>("root.type.planes", "plane");
// TODO add edges, but there's no information on how to
add_edge(vertex(0, g), vertex(2, g), "This demo edge has no properties", g);
add_edge(vertex(2, g), vertex(1, g), "One more", g);
for (Vertex v : boost::make_iterator_range(vertices(g))) {
std::cout << g[v] << "\n";
}
{
auto vindex = get(boost::vertex_index, g);
auto calc_color = [&](Vertex v) { return g[v].which()? "red":"blue"; };
auto calc_label = [&](Vertex v) {
// multiline Mrecord label formatting
auto txt = boost::lexical_cast<std::string>(g[v]);
boost::algorithm::replace_all(txt, "\n --", "|");
return "{" + txt + "}";
};
boost::dynamic_properties dp;
dp.property("node_id", vindex);
dp.property("label", boost::make_transform_value_property_map(calc_label, vindex));
dp.property("fontcolor", boost::make_transform_value_property_map(calc_color, vindex));
dp.property("style", boost::make_static_property_map<Vertex>(std::string("filled")));
dp.property("label", get(boost::edge_bundle, g));
auto pw = boost::dynamic_vertex_properties_writer { dp, "node_id" };
using Map = std::map<std::string, std::string>;
auto gpw = boost::make_graph_attributes_writer(Map{}, Map {{"shape", "Mrecord"}}, Map{});
std::ofstream ofs("graph.dot");
write_graphviz(ofs, g, pw, pw, gpw);
}
}
Prints the output shown above, as well as the following graph.dot:
digraph G {
node [
shape=Mrecord];
0 [fontcolor=blue, label="{Name: Garfield, Weight: 4Kg| [e_54; petrol; 56]| [e_52; diesel; 52]| [m_22; electric; 51]| additions [steam; 153/0/1/0]}", style=filled];
1 [fontcolor=blue, label="{Name: Awesome, Weight: 3Kg| [t_54; petrol; 16]| [t_52; wind; 62]| [t_22; electric; 81]| additions [water; 123/1/1/0]}", style=filled];
2 [fontcolor=red, label="{Id: 231, Name: abc| utilities [e456; 567]}", style=filled];
0->2 [label="This demo edge has no properties"];
2->1 [label="One more"];
}
Suppose I am iterating over a boost::multi_index using one of the two indices. Then if I start iterating using the other index and erasing an element according some criteria, does that invalidate the upper iterator?
E.g.,
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <string>
#include <iostream>
using namespace boost::multi_index;
using namespace std;
struct person {
string name;
string last_name;
int age;
};
typedef multi_index_container<
person,
indexed_by<
ordered_non_unique<member<person, string, &person::name> >,
ordered_non_unique<member<person, int, &person::age> >,
sequenced<>
>
> PersonCollection;
int main()
{
PersonCollection pc;
//insert some elements into pc
struct person kk = {"John", "Doe", 15};
pc.insert(kk);
kk = {"John", "Doe2", 17};
pc.insert(kk);
kk = {"John", "Doe3", 34};
pc.insert(kk);
kk = {"John", "Smith", 34};
pc.insert(kk);
auto &index0 = pc.get<0>();
auto range0 = index0.equal_range("John");
while (range0.first != range0.second) {
auto &index1 = pc.get<1>();
auto range1 = index1.equal_range(34);
while (range1.first != range1.second) {
if (range1.first->last_name == "Smith")
range1.first = index1.erase(range1.first);
else
++range1.first;
}
++range0.first;
}
return 0;
}
So in this case, is the range0 iterator always valid? Thanks!
Your loop is likely fundamentally flawed. I think this is because you used confusing names (range0 etc.). You outer loop is unrelated to the inner loops so, you could have removed it (saving time doing useless repeats). Here's a clarified rewrite:
auto& ageIdx = pc.get<byAge>();
auto namerange = pc.get<byName>().equal_range("John");
for (auto name_it = namerange.first, end = namerange.second; name_it != end; ++name_it) {
auto agerange = ageIdx.equal_range(34);
for (auto age_it = agerange.first, end = agerange.second; age_it != end;) {
if (age_it->last_name == "Smith")
age_it = ageIdx.erase(age_it);
else
++age_it;
}
}
Indeed this is unsafe. The name_it might be invalidated. Fix it:
for (auto name_it = namerange.first, end = namerange.second; name_it != end;) {
++name_it;
Note the code will remove remove 'Ruby Smith (age 34)' just fine. The 'John' criterion is never used, so:
Even better, fix it to the equivalent:
auto agerange = pc.get<byAge>().equal_range(34);
for (auto age_it = agerange.first, end = agerange.second; age_it != end;) {
std::cout << *age_it << "\n";
if (age_it->last_name == "Smith")
age_it = ageIdx.erase(age_it);
else
++age_it;
}
Thinking out of the box
Really looks like you want to delete by some "primary" key (name, last_name, age). Say what you mean:
pc.erase(pc.find(boost::make_tuple("Smith", "John", 34))); // just the one
That's all of the code.
Live On Coliru
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <string>
#include <iostream>
struct person {
std::string name;
std::string last_name;
int age;
friend std::ostream& operator<<(std::ostream& os, person const& p) {
return os << "{" << p.name << ", " << p.last_name << ", " << p.age << "}";
}
};
namespace bmi = boost::multi_index;
typedef bmi::multi_index_container<
person,
bmi::indexed_by<
bmi::ordered_unique<bmi::tag<struct primaryKey>,
bmi::composite_key<person,
bmi::member<person, std::string, &person::last_name>,
bmi::member<person, std::string, &person::name>,
bmi::member<person, int, &person::age>
>
>,
bmi::ordered_non_unique<bmi::tag<struct byAge>, bmi::member<person, int, &person::age> >
>
> PersonCollection;
int main() {
PersonCollection pc {
person { "John", "Lennon", 34 },
person { "Elliot", "Gardiner", 72 },
person { "John", "Smith", 34 },
person { "Lucy", "Greenstle", 34 },
person { "Gold", "Smith", 34 },
person { "Nicolai", "Josuttis", 42 }
};
auto& idx = pc.get<primaryKey>();
// print
std::copy(pc.begin(), pc.end(), std::ostream_iterator<person>(std::cout << "\n", "; "));
pc.erase(pc.find(boost::make_tuple("Smith", "John", 34))); // just the one
// print
std::copy(pc.begin(), pc.end(), std::ostream_iterator<person>(std::cout << "\n", "; "));
auto range = idx.equal_range(boost::make_tuple("Smith", "John")); // any age
for (auto f=range.first, l=range.second; f!=l;)
f = idx.erase(f);
// print
std::copy(pc.begin(), pc.end(), std::ostream_iterator<person>(std::cout << "\n", "; "));
range = idx.equal_range(boost::make_tuple("Smith")); // any age/first name
for (auto f=range.first, l=range.second; f!=l;)
f = idx.erase(f);
// print
std::copy(pc.begin(), pc.end(), std::ostream_iterator<person>(std::cout << "\n", "; "));
}
Prints:
{Elliot, Gardiner, 72}; {Lucy, Greenstle, 34}; {Nicolai, Josuttis, 42}; {John, Lennon, 34}; {Gold, Smith, 34}; {John, Smith, 34};
{Elliot, Gardiner, 72}; {Lucy, Greenstle, 34}; {Nicolai, Josuttis, 42}; {John, Lennon, 34}; {Gold, Smith, 34};
{Elliot, Gardiner, 72}; {Lucy, Greenstle, 34}; {Nicolai, Josuttis, 42}; {John, Lennon, 34}; {Gold, Smith, 34};
{Elliot, Gardiner, 72}; {Lucy, Greenstle, 34}; {Nicolai, Josuttis, 42}; {John, Lennon, 34};
I want to use boost's dijkstra algorithm (since I'm using boost in other parts of my program). The problem I'm having is adding custom objects (I believe they are referred to as property) to the adjacency_list.
Essentially I have a custom edge class that maintains all sorts of information regarding the edge and the vertices that are connected through it. I want to store my custom data object with the edge properties that are required by the adjaceny_list
I've successfully implemented the toy example that boost provides. I've tried to use custom properties to no avail (boost example, boost properties). I'm fine with just encapsulating my VEdge data structure in a struct or something, I just need to be able to retrieve it. But I haven't been able to figure out how to include my custom data structure into the boost adjaceny_list structure.
In my case I have the following program:
Main.cpp:
#include <iostream>
#include <fstream>
#include "dijkstra.h"
#include <vector>
int
main(int, char *[])
{
// Generate the vector of edges from elsewhere in the program
std::vector<VEdge*> edges; //someclass.get_edges();
td* test = new td(edges);
test->run_d();
test->print_path();
return EXIT_SUCCESS;
}
Dijkstra.cpp:
#include <iostream>
#include <fstream>
#include "dijkstra.h"
using namespace boost;
td::td() {
kNumArcs = sizeof(kEdgeArray) / sizeof(Edge);
kNumNodes = 5;
}
td::td(std::vector<VEdge*> edges) {
// add edges to the edge property here
for(VEdge* e : edges) {
// for each edge, add to the kEdgeArray variable in some way
// The boost example forces the input to be an array of edge_property type.
// So here is where I will convert my raw VEdge data structure to
// the custom edge_property that I am struggling to understand how to create.
}
kNumArcs = sizeof(kEdgeArray) / sizeof(Edge);
kNumNodes = 5;
}
void td::run_d() {
kGraph = graph_t(kEdgeArray, kEdgeArray + kNumArcs, kWeights, kNumNodes);
kWeightMap = get(edge_weight, kGraph);
kP = std::vector<vertex_descriptor >(num_vertices(kGraph));
kD = std::vector<int>(num_vertices(kGraph));
kS = vertex(A, kGraph);
dijkstra_shortest_paths(kGraph, kS,
predecessor_map(boost::make_iterator_property_map(kP.begin(), get(boost::vertex_index, kGraph))).
distance_map(boost::make_iterator_property_map(kD.begin(), get(boost::vertex_index, kGraph))));
}
void td::print_path() {
std::cout << "distances and parents:" << std::endl;
graph_traits < graph_t >::vertex_iterator vi, vend;
for (boost::tie(vi, vend) = vertices(kGraph); vi != vend; ++vi) {
std::cout << "distance(" << kName[*vi] << ") = " << kD[*vi] << ", ";
std::cout << "parent(" << kName[*vi] << ") = " << kName[kP[*vi]] << std::
endl;
}
}
void td::generate_dot_file() {
std::cout << std::endl;
std::ofstream dot_file("figs/dijkstra-eg.dot");
dot_file << "digraph D {\n"
<< " rankdir=LR\n"
<< " size=\"4,3\"\n"
<< " ratio=\"fill\"\n"
<< " edge[style=\"bold\"]\n" << " node[shape=\"circle\"]\n";
graph_traits < graph_t >::edge_iterator ei, ei_end;
for (boost::tie(ei, ei_end) = edges(kGraph); ei != ei_end; ++ei) {
graph_traits < graph_t >::edge_descriptor e = *ei;
graph_traits < graph_t >::vertex_descriptor
u = source(e, kGraph), v = target(e, kGraph);
dot_file << kName[u] << " -> " << kName[v]
<< "[label=\"" << get(kWeightMap, e) << "\"";
if (kP[v] == u)
dot_file << ", color=\"black\"";
else
dot_file << ", color=\"grey\"";
dot_file << "]";
}
dot_file << "}";
}
Dijkstra.h:
#ifndef _TEMPD_H_
#define _TEMPD_H_
#pragma once
#include <boost/config.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/property_map/property_map.hpp>
using namespace boost;
typedef adjacency_list < listS, vecS, directedS,
no_property, property < edge_weight_t, int > > graph_t;
typedef graph_traits < graph_t >::vertex_descriptor vertex_descriptor;
typedef std::pair<int, int> Edge;
struct VEdge{
// custom variables here
VNode start;
VNode end;
int weight;
int id;
// other irrelevant data pertinent to my program that must be preserved
};
struct VNode {
// custom variables here
int x;
int y;
int id;
// other irrelevant data pertinent to my program that must be preserved
}
enum nodes { A, B, C, D, E };
class td {
public:
td();
td(std::vector<VEdge*>);
~td();
void run_d();
void print_path();
void generate_dot_file();
private:
Edge kEdgeArray[9] = { Edge(A, C), Edge(B, B), Edge(B, D), Edge(B, E),
Edge(C, B), Edge(C, D), Edge(D, E), Edge(E, A), Edge(E, B)
};
char kName[5] = {'A','B','C','D','E'};
int kWeights[9] = { 1, 2, 1, 2, 7, 3, 1, 1, 1 };
int kNumArcs;
int kNumNodes;
vertex_descriptor kS;
graph_t kGraph;
std::vector<int> kD;
std::vector<vertex_descriptor> kP;
property_map<graph_t, edge_weight_t>::type kWeightMap;
};
#endif
I know my example is a bit contrived, but it communicates what I'm trying to accomplish. I know I need a custom data structure for my edge_descriptor which gets sent to the graph_t typedef.
So I'm looking to alter my Dijkstra.h file to look something like this:
struct vertex_label_t {vertex_property_tag kind;};
struct edge_label_t {edge_property_tag kind;};
typedef property <vertex_custom_t, VNode*>,
property <vertex_label_t, string>,
property <vertex_root_t, ing> > > vertex_p;
typedef property <edge_custom_t, VEdge*>,
property <edge_label_t, string > > edge_p;
typedef adjacency_list < listS, vecS, directedS,
vertex_p, edge_p > graph_t;
typedef graph_traits < graph_t >::vertex_descriptor vertex_descriptor;
Okay. You've come a long ways since https://stackoverflow.com/questions/28889423/boost-adjacency-list-swap-errors-when-using-boost-dijkstra; the sample is self-contained and can compile¹
I figured I could just connect some dots and hope this would be helpful.
1. Using VEdge
For the simplest option, I'd use Bundled Properties, and define VEdge as follows:
struct VEdge {
int id;
int source, target;
double weight;
// custom variables here
};
Now, we define the graph as
using graph_t = boost::adjacency_list<boost::listS, boost::vecS,
boost::directedS, boost::no_property, VEdge>;
using weight_map_t = boost::property_map<graph_t, double VEdge::*>::type;
As you can see the weight-map has a little more complicated type, as documented under Properties maps from bundled properties. You can get the actual map:
weight_map_t kWeightMap = boost::get(&VEdge::weight, kGraph);
Now, let's recreate the test data from your question in a vector of VEdge (A=0...E=4):
std::vector<VEdge> edges {
{ 2100, 0, 2, 1 },
{ 2101, 1, 1, 2 },
{ 2102, 1, 3, 1 },
{ 2103, 1, 4, 2 },
{ 2104, 2, 1, 7 },
{ 2105, 2, 3, 3 },
{ 2106, 3, 4, 1 },
{ 2107, 4, 0, 1 },
{ 2108, 4, 1, 1 },
};
test_dijkstra test(edges);
The constructor has a little bit of complication to find the number of vertices from just the edges. I used Boost Range algorithms to find the maximum vertex node id and pass that:
test_dijkstra::test_dijkstra(std::vector<VEdge> edges) {
using namespace boost::adaptors;
size_t max_node;
boost::partial_sort_copy(
edges | transformed([](VEdge const &e) -> size_t { return std::max(e.source, e.target); }),
boost::make_iterator_range(&max_node, &max_node + 1),
std::greater<size_t>());
auto e = edges | transformed([](VEdge const &ve) { return std::make_pair(ve.source, ve.target); });
kGraph = graph_t(e.begin(), e.end(), edges.begin(), max_node + 1);
}
Note how edges.begin() can be passed: it is not "forced to be a an array of edge_property type". An iterator will be fine.
Now the dijkstra needs to get the weight_map argument because it's no longer the default internal property:
void test_dijkstra::run_dijkstra() {
weight_map_t kWeightMap = boost::get(&VEdge::weight, kGraph);
vertex_descriptor kS = vertex(0, kGraph);
kP = std::vector<vertex_descriptor>(num_vertices(kGraph));
kD = std::vector<int>(num_vertices(kGraph));
dijkstra_shortest_paths(
kGraph, kS,
predecessor_map(boost::make_iterator_property_map(kP.begin(), get(boost::vertex_index, kGraph)))
.distance_map(boost::make_iterator_property_map(kD.begin(), get(boost::vertex_index, kGraph)))
.weight_map(kWeightMap));
}
For this sample, I translated A to 0 as the starting vertex. The result path is exactly the same as for the original²
Full Program
Live On Coliru
#include <boost/config.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/adaptors.hpp>
#include <fstream>
#include <iostream>
struct VEdge {
int id;
int source, target;
double weight;
// custom variables here
};
class test_dijkstra {
using graph_t = boost::adjacency_list<boost::listS, boost::vecS, boost::directedS, boost::no_property, VEdge>;
using vertex_descriptor = boost::graph_traits<graph_t>::vertex_descriptor;
using edge_descriptor = boost::graph_traits<graph_t>::edge_descriptor;
using weight_map_t = boost::property_map<graph_t, double VEdge::*>::type;
public:
test_dijkstra(std::vector<VEdge>);
~test_dijkstra() {}
void run_dijkstra();
void print_path();
void generate_dot_file();
private:
graph_t kGraph;
std::vector<int> kD;
std::vector<vertex_descriptor> kP;
};
test_dijkstra::test_dijkstra(std::vector<VEdge> edges) {
using namespace boost::adaptors;
size_t max_node;
boost::partial_sort_copy(
edges | transformed([](VEdge const &e) -> size_t { return std::max(e.source, e.target); }),
boost::make_iterator_range(&max_node, &max_node + 1),
std::greater<size_t>());
auto e = edges | transformed([](VEdge const &ve) { return std::make_pair(ve.source, ve.target); });
kGraph = graph_t(e.begin(), e.end(), edges.begin(), max_node + 1);
}
void test_dijkstra::run_dijkstra() {
weight_map_t kWeightMap = boost::get(&VEdge::weight, kGraph);
vertex_descriptor kS = vertex(0, kGraph);
kP = std::vector<vertex_descriptor>(num_vertices(kGraph));
kD = std::vector<int>(num_vertices(kGraph));
dijkstra_shortest_paths(
kGraph, kS,
predecessor_map(boost::make_iterator_property_map(kP.begin(), get(boost::vertex_index, kGraph)))
.distance_map(boost::make_iterator_property_map(kD.begin(), get(boost::vertex_index, kGraph)))
.weight_map(kWeightMap));
}
void test_dijkstra::print_path() {
std::cout << "distances and parents:" << std::endl;
boost::graph_traits<graph_t>::vertex_iterator vi, vend;
for (boost::tie(vi, vend) = vertices(kGraph); vi != vend; ++vi) {
std::cout << "distance(" << *vi << ") = " << kD[*vi] << ", ";
std::cout << "parent(" << *vi << ") = " << kP[*vi] << "\n";
}
}
void test_dijkstra::generate_dot_file() {
weight_map_t kWeightMap = boost::get(&VEdge::weight, kGraph);
std::ofstream dot_file("figs/dijkstra-eg.dot");
dot_file << "digraph D {\n"
<< " rankdir=LR\n"
<< " size=\"4,3\"\n"
<< " ratio=\"fill\"\n"
<< " edge[style=\"bold\"]\n"
<< " node[shape=\"circle\"]\n";
boost::graph_traits<graph_t>::edge_iterator ei, ei_end;
for (boost::tie(ei, ei_end) = edges(kGraph); ei != ei_end; ++ei) {
boost::graph_traits<graph_t>::edge_descriptor e = *ei;
boost::graph_traits<graph_t>::vertex_descriptor u = source(e, kGraph), v = target(e, kGraph);
dot_file << u << " -> " << v << "[label=\"" << get(kWeightMap, e) << "\"";
if (kP[v] == u)
dot_file << ", color=\"black\"";
else
dot_file << ", color=\"grey\"";
dot_file << "]";
}
dot_file << "}";
}
int main() {
std::vector<VEdge> edges {
{ 2100, 0, 2, 1 },
{ 2101, 1, 1, 2 },
{ 2102, 1, 3, 1 },
{ 2103, 1, 4, 2 },
{ 2104, 2, 1, 7 },
{ 2105, 2, 3, 3 },
{ 2106, 3, 4, 1 },
{ 2107, 4, 0, 1 },
{ 2108, 4, 1, 1 },
};
test_dijkstra test(edges);
test.run_dijkstra();
test.print_path();
test.generate_dot_file();
}
2. Using VEdge*
If you insist on using the pointers in the properties a few things become more complicated:
you'll need to manage the lifetime of the elements
you can't use the double VEdge::* weight_map_t. Instead, you'll need to adapt a custom propertymap for this:
auto kWeightMap = boost::make_transform_value_property_map(
[](VEdge* ve) { return ve->weight; },
boost::get(boost::edge_bundle, kGraph)
);
On the bright side, you can use the short-hand indexer notation to evaluate edge properties from an edge_descriptor as shown in generate_dot_file():
dot_file << u << " -> " << v << "[label=\"" << kGraph[e]->weight << "\"";
Of course this approach avoids copying the VEdge objects into the bundle, so it could be more efficient
Without further ado (and without bothering about the memory leaks):
Live On Coliru
#include <boost/config.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <fstream>
#include <iostream>
struct VEdge {
int id;
int source, target;
double weight;
// custom variables here
};
class test_dijkstra {
using graph_t = boost::adjacency_list<boost::listS, boost::vecS, boost::directedS, boost::no_property, VEdge*>;
using vertex_descriptor = boost::graph_traits<graph_t>::vertex_descriptor;
using edge_descriptor = boost::graph_traits<graph_t>::edge_descriptor;
public:
test_dijkstra(std::vector<VEdge*>);
~test_dijkstra() {}
void run_dijkstra();
void print_path();
void generate_dot_file();
private:
graph_t kGraph;
std::vector<int> kD;
std::vector<vertex_descriptor> kP;
};
test_dijkstra::test_dijkstra(std::vector<VEdge*> edges) {
using namespace boost::adaptors;
size_t max_node;
boost::partial_sort_copy(
edges | transformed([](VEdge const* e) -> size_t { return std::max(e->source, e->target); }),
boost::make_iterator_range(&max_node, &max_node + 1),
std::greater<size_t>());
auto e = edges | transformed([](VEdge const *ve) { return std::make_pair(ve->source, ve->target); });
kGraph = graph_t(e.begin(), e.end(), edges.begin(), max_node + 1);
}
void test_dijkstra::run_dijkstra() {
auto kWeightMap = boost::make_transform_value_property_map(
[](VEdge* ve) { return ve->weight; },
boost::get(boost::edge_bundle, kGraph)
);
vertex_descriptor kS = vertex(0, kGraph);
kP = std::vector<vertex_descriptor>(num_vertices(kGraph));
kD = std::vector<int>(num_vertices(kGraph));
dijkstra_shortest_paths(
kGraph, kS,
predecessor_map(boost::make_iterator_property_map(kP.begin(), get(boost::vertex_index, kGraph)))
.distance_map(boost::make_iterator_property_map(kD.begin(), get(boost::vertex_index, kGraph)))
.weight_map(kWeightMap));
}
void test_dijkstra::print_path() {
std::cout << "distances and parents:" << std::endl;
boost::graph_traits<graph_t>::vertex_iterator vi, vend;
for (boost::tie(vi, vend) = vertices(kGraph); vi != vend; ++vi) {
std::cout << "distance(" << *vi << ") = " << kD[*vi] << ", ";
std::cout << "parent(" << *vi << ") = " << kP[*vi] << "\n";
}
}
void test_dijkstra::generate_dot_file() {
std::ofstream dot_file("figs/dijkstra-eg.dot");
dot_file << "digraph D {\n"
<< " rankdir=LR\n"
<< " size=\"4,3\"\n"
<< " ratio=\"fill\"\n"
<< " edge[style=\"bold\"]\n"
<< " node[shape=\"circle\"]\n";
boost::graph_traits<graph_t>::edge_iterator ei, ei_end;
for (boost::tie(ei, ei_end) = edges(kGraph); ei != ei_end; ++ei) {
boost::graph_traits<graph_t>::edge_descriptor e = *ei;
boost::graph_traits<graph_t>::vertex_descriptor u = source(e, kGraph), v = target(e, kGraph);
dot_file << u << " -> " << v << "[label=\"" << kGraph[e]->weight << "\"";
if (kP[v] == u)
dot_file << ", color=\"black\"";
else
dot_file << ", color=\"grey\"";
dot_file << "]";
}
dot_file << "}";
}
int main() {
std::vector<VEdge*> edges {
new VEdge { 2100, 0, 2, 1 },
new VEdge { 2101, 1, 1, 2 },
new VEdge { 2102, 1, 3, 1 },
new VEdge { 2103, 1, 4, 2 },
new VEdge { 2104, 2, 1, 7 },
new VEdge { 2105, 2, 3, 3 },
new VEdge { 2106, 3, 4, 1 },
new VEdge { 2107, 4, 0, 1 },
new VEdge { 2108, 4, 1, 1 },
};
test_dijkstra test(edges);
test.run_dijkstra();
test.print_path();
test.generate_dot_file();
}
¹ after swatting silly typos
² self-contained Live On Coliru
I have problems getting to compile the BFS of a very simple graph. Whatever I do I get various compiler messages about unmatched method calls (I've tried boost::visitor and extending boost::default_bfs_visitor etc.)
#include <stdint.h>
#include <iostream>
#include <vector>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/breadth_first_search.hpp>
int main() {
typedef boost::adjacency_list<boost::vecS, boost::hash_setS, boost::undirectedS, uint32_t, uint32_t, boost::no_property> graph_t;
graph_t graph(4);
graph_t::vertex_descriptor a = boost::vertex(0, graph);
graph_t::vertex_descriptor b = boost::vertex(1, graph);
graph_t::vertex_descriptor c = boost::vertex(2, graph);
graph_t::vertex_descriptor d = boost::vertex(3, graph);
graph[a] = 0;
graph[b] = 1;
graph[c] = 2;
graph[d] = 3;
std::pair<graph_t::edge_descriptor, bool> result = boost::add_edge(a, b, 0, graph);
result = boost::add_edge(a, c, 1, graph);
result = boost::add_edge(c, b, 2, graph);
class {
public:
void initialize_vertex(const graph_t::vertex_descriptor &s, graph_t &g) {
std::cout << "Initialize: " << g[s] << std::endl;
}
void discover_vertex(const graph_t::vertex_descriptor &s, graph_t &g) {
std::cout << "Discover: " << g[s] << std::endl;
}
void examine_vertex(const graph_t::vertex_descriptor &s, graph_t &g) {
std::cout << "Examine vertex: " << g[s] << std::endl;
}
void examine_edge(const graph_t::edge_descriptor &e, graph_t &g) {
std::cout << "Examine edge: " << g[e] << std::endl;
}
void tree_edge(const graph_t::edge_descriptor &e, graph_t &g) {
std::cout << "Tree edge: " << g[e] << std::endl;
}
void non_tree_edge(const graph_t::edge_descriptor &e, graph_t &g) {
std::cout << "Non-Tree edge: " << g[e] << std::endl;
}
void gray_target(const graph_t::edge_descriptor &e, graph_t &g) {
std::cout << "Gray target: " << g[e] << std::endl;
}
void black_target(const graph_t::edge_descriptor &e, graph_t &g) {
std::cout << "Black target: " << g[e] << std::endl;
}
void finish_vertex(const graph_t::vertex_descriptor &s, graph_t &g) {
std::cout << "Finish vertex: " << g[s] << std::endl;
}
} bfs_visitor;
boost::breadth_first_search(graph, a, bfs_visitor);
return 0;
}
How to visit the graph using bfs_visitor?
PS. I've seen and compiled "How to create a C++ Boost undirected graph and traverse it in depth first search (DFS) order?" but it didn't help.
You can see here a list of the overloads of breadth_first_search. If you don't want to specify every one of the parameters you need to use the named-parameter version. It would look like this:
breadth_first_search(graph, a, boost::visitor(bfs_visitor));
This would work as is if you had used vecS as your VertexList storage in your graph definition or if you had constructed and initialized an internal vertex_index property map. Since you are using hash_setS you need to change the invocation to:
breath_first_search(graph, a, boost::visitor(bfs_visitor).vertex_index_map(my_index_map));
You are already using an index map in your uint32_t bundled property. You can use get(boost::vertex_bundle, graph) to access it.
There was also a problem with your visitor. You should derive it from boost::default_bfs_visitor and the graph_t parameter of your member functions needs to be const qualified.
Full code:
#include <stdint.h>
#include <iostream>
#include <vector>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/breadth_first_search.hpp>
typedef boost::adjacency_list<boost::vecS, boost::hash_setS, boost::undirectedS, uint32_t, uint32_t, boost::no_property> graph_t;
struct my_visitor : boost::default_bfs_visitor{
void initialize_vertex(const graph_t::vertex_descriptor &s, const graph_t &g) const {
std::cout << "Initialize: " << g[s] << std::endl;
}
void discover_vertex(const graph_t::vertex_descriptor &s, const graph_t &g) const {
std::cout << "Discover: " << g[s] << std::endl;
}
void examine_vertex(const graph_t::vertex_descriptor &s, const graph_t &g) const {
std::cout << "Examine vertex: " << g[s] << std::endl;
}
void examine_edge(const graph_t::edge_descriptor &e, const graph_t &g) const {
std::cout << "Examine edge: " << g[e] << std::endl;
}
void tree_edge(const graph_t::edge_descriptor &e, const graph_t &g) const {
std::cout << "Tree edge: " << g[e] << std::endl;
}
void non_tree_edge(const graph_t::edge_descriptor &e, const graph_t &g) const {
std::cout << "Non-Tree edge: " << g[e] << std::endl;
}
void gray_target(const graph_t::edge_descriptor &e, const graph_t &g) const {
std::cout << "Gray target: " << g[e] << std::endl;
}
void black_target(const graph_t::edge_descriptor &e, const graph_t &g) const {
std::cout << "Black target: " << g[e] << std::endl;
}
void finish_vertex(const graph_t::vertex_descriptor &s, const graph_t &g) const {
std::cout << "Finish vertex: " << g[s] << std::endl;
}
};
int main() {
graph_t graph(4);
graph_t::vertex_descriptor a = boost::vertex(0, graph);
graph_t::vertex_descriptor b = boost::vertex(1, graph);
graph_t::vertex_descriptor c = boost::vertex(2, graph);
graph_t::vertex_descriptor d = boost::vertex(3, graph);
graph[a] = 0;
graph[b] = 1;
graph[c] = 2;
graph[d] = 3;
std::pair<graph_t::edge_descriptor, bool> result = boost::add_edge(a, b, 0, graph);
result = boost::add_edge(a, c, 1, graph);
result = boost::add_edge(c, b, 2, graph);
my_visitor vis;
breadth_first_search(graph, a, boost::visitor(vis).vertex_index_map(get(boost::vertex_bundle,graph)));
return 0;
}
I faced the same problem, but compared to the answer provided by user1252091 my vertex type is a struct that doesn't include an integer that can be used to create a vertex_index_map, therefore the line
breadth_first_search(graph, a, boost::visitor(vis).vertex_index_map(get(boost::vertex_bundle,graph)));
wouldn't work in my case. Eventually I figured out how to create an external vertex_index_map (thanks also to this answer) and pass it to the breadth_first_search function. Here is a working example in case it helps others:
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/visitors.hpp>
#include <boost/graph/breadth_first_search.hpp>
#include <iostream>
struct Person
{
std::string Name;
unsigned int YearBorn;
};
typedef boost::adjacency_list <boost::vecS, boost::hash_setS, boost::bidirectionalS, Person, boost::no_property > FamilyTree;
typedef boost::graph_traits<FamilyTree>::vertex_descriptor Vertex;
typedef boost::graph_traits<FamilyTree>::edge_descriptor Edge;
template <class Graph>
class BfsVisitor : public boost::default_bfs_visitor
{
public:
typedef typename boost::graph_traits<Graph>::vertex_descriptor VertexDescriptor;
typedef typename boost::graph_traits<Graph>::edge_descriptor EdgeDescriptor;
BfsVisitor(std::vector<VertexDescriptor>& nodesVisited)
: m_nodesVisited(nodesVisited){}
void tree_edge(EdgeDescriptor e, const Graph& g) const
{
VertexDescriptor u = source(e, g);
VertexDescriptor v = target(e, g);
m_nodesVisited.push_back(v);
}
private:
std::vector<VertexDescriptor>& m_nodesVisited;
};
const Person Abe_Simpson {"Abe_Simpson", 0};
const Person Mona_Simpson { "Mona_Simpson", 0};
const Person Herb_Simpson { "Herb_Simpson", 0};
const Person Homer_Simpson { "Homer_Simpson", 0};
const Person Clancy_Bouvier { "Clancy_Bouvier", 0};
const Person Jacqueline_Bouvier { "Jacqueline_Bouvier", 0};
const Person Marge_Bouvier { "Marge_Bouvier", 0};
const Person Patty_Bouvier { "Patty_Bouvier", 0};
const Person Selma_Bouvier { "Selma_Bouvier", 0};
const Person Bart_Simpson { "Bart_Simpson", 0};
const Person Lisa_Simpson { "Lisa_Simpson", 0};
const Person Maggie_Simpson { "Maggie_Simpson", 0};
const Person Ling_Bouvier { "Ling_Bouvier", 0};
int main(void)
{
std::cout << __FUNCTION__ << "\n";
FamilyTree g;
// nodes
auto v_Abe_Simpson = boost::add_vertex(Abe_Simpson,g);
auto v_Mona_Simpson = boost::add_vertex(Mona_Simpson,g);
auto v_Herb_Simpson = boost::add_vertex(Herb_Simpson,g);
auto v_Homer_Simpson = boost::add_vertex(Homer_Simpson,g);
auto v_Clancy_Bouvier = boost::add_vertex(Clancy_Bouvier,g);
auto v_Jacqueline_Bouvier = boost::add_vertex(Jacqueline_Bouvier,g);
auto v_Marge_Bouvier = boost::add_vertex(Marge_Bouvier,g);
auto v_Patty_Bouvier = boost::add_vertex(Patty_Bouvier,g);
auto v_Selma_Bouvier = boost::add_vertex(Selma_Bouvier,g);
auto v_Bart_Simpson = boost::add_vertex(Bart_Simpson,g);
auto v_Lisa_Simpson = boost::add_vertex(Lisa_Simpson,g);
auto v_Maggie_Simpson = boost::add_vertex(Maggie_Simpson,g);
auto v_Ling_Bouvier = boost::add_vertex(Ling_Bouvier,g);
// connections
boost::add_edge(v_Abe_Simpson, v_Herb_Simpson, g);
boost::add_edge(v_Abe_Simpson, v_Homer_Simpson, g);
boost::add_edge(v_Mona_Simpson, v_Herb_Simpson, g);
boost::add_edge(v_Mona_Simpson, v_Homer_Simpson, g);
boost::add_edge(v_Clancy_Bouvier, v_Marge_Bouvier, g);
boost::add_edge(v_Clancy_Bouvier, v_Patty_Bouvier, g);
boost::add_edge(v_Clancy_Bouvier, v_Selma_Bouvier, g);
boost::add_edge(v_Jacqueline_Bouvier, v_Marge_Bouvier, g);
boost::add_edge(v_Jacqueline_Bouvier, v_Patty_Bouvier, g);
boost::add_edge(v_Jacqueline_Bouvier, v_Selma_Bouvier, g);
boost::add_edge(v_Homer_Simpson, v_Bart_Simpson, g);
boost::add_edge(v_Homer_Simpson, v_Lisa_Simpson, g);
boost::add_edge(v_Homer_Simpson, v_Maggie_Simpson, g);
boost::add_edge(v_Marge_Bouvier, v_Bart_Simpson, g);
boost::add_edge(v_Marge_Bouvier, v_Lisa_Simpson, g);
boost::add_edge(v_Marge_Bouvier, v_Maggie_Simpson, g);
boost::add_edge(v_Selma_Bouvier, v_Ling_Bouvier, g);
typedef std::map<Vertex, size_t>IndexMap;
IndexMap mapIndex;
boost::associative_property_map<IndexMap> propmapIndex(mapIndex);
size_t i=0;
FamilyTree::vertex_iterator vi, vi_end;
for (boost::tie(vi, vi_end) = boost::vertices(g); vi != vi_end; ++vi)
{
boost::put(propmapIndex, *vi, i++);
}
for (boost::tie(vi, vi_end) = boost::vertices(g); vi != vi_end; ++vi)
{
Vertex vParent = *vi;
std::vector<Vertex> vertexDescriptors;
BfsVisitor<FamilyTree> bfsVisitor(vertexDescriptors);
breadth_first_search(g, vParent, visitor(bfsVisitor).vertex_index_map(propmapIndex));
std::cout << "\nDecendants of " << g[vParent].Name << ":\n";
for (auto v : vertexDescriptors)
{
Person p = g[v];
std::cout << p.Name << "\n";
}
}
getchar();
return 0;
}