1

I have a adjacency list is boost::adjacency_list Where GraphData is a structure contains name

struct GraphData
{
     std::string Divison;
     std::vector<std::pair<std::string, int>> studentInfo;
     // Student info contains Name and age
     // Note I don't want to use anyother stucture inside GraphItem
}

How to Read and write this student info using dynamic property ?

void WriteGraph()
{
   boost::dynamic_properties dp;
   dp.property("Division", boost::get(&GraphItem::Division, graph));
   boost::write_graphml(filename, graph, dp, true); 
}

void ReadGraph()
{
   boost::dynamic_properties dp;
   std::ifstream file(fileName);
   boost::read_graphml(file, graph, dp);
}

1 Answers1

1

In response to the older question I had this:

A few approaches

Opaque Member Object

One way would be to treat the student property just like the Name property:

dp.property("Name", boost::get(&GraphData::Name, graph));
dp.property("Student", boost::get(&GraphData::student, graph));

All that's required is you tell the standard library how to stream Student objects:

inline static std::ostream& operator<<(std::ostream& os, Student const& s) {
    return os << s.roll_no << " " << std::quoted(s.division);
}
inline static std::istream& operator>>(std::istream& is, Student& s) {
    return is >> s.roll_no >> std::ws >> std::quoted(s.division);
}

You'll get XML Like Live On Wandbox.

<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd"> Mary-Anne Hornam 80 "alchemy" Mary-Anne Bufgloon 57 "drama" Joyce Preet 8 "drama" Philomena Joyce 3 "drama" James Tarsinck 78 "science"

Separate Properties

If you really want the separate GraphML properties roll_no and division, then you go back to your transform_value_property_map:

void WriteGraph(std::ostream &os, Graph &graph) {
    boost::dynamic_properties dp;
    auto roll_no =  [](Student const& s) { return s.roll_no; };
    auto division = [](Student const& s) { return s.division; };

    dp.property("Name", get(&GraphData::Name, graph));
    dp.property("roll_no", boost::make_transform_value_property_map(roll_no,

get(&GraphData::student, graph))); dp.property("division", boost::make_transform_value_property_map(division, get(&GraphData::student, graph)));

    boost::write_graphml(os, graph, dp, true);
}

Note that for reading purposes, a LvaluePropertyMap is required, so it needs to be written slightly more permissive:

Graph ReadGraph(std::string const &fileName) {
    Graph graph;
    boost::dynamic_properties dp;
    auto roll_no =  [](Student& s) ->auto& { return s.roll_no; };
    auto division = [](Student& s) ->auto& { return s.division; };

    dp.property("Name", get(&GraphData::Name, graph));
    dp.property("roll_no", boost::make_transform_value_property_map(roll_no,

get(&GraphData::student, graph))); dp.property("division", boost::make_transform_value_property_map(division, get(&GraphData::student, graph)));

    std::ifstream file(fileName);
    boost::read_graphml(file, graph, dp);

    return graph;
}

Now, you'll get XML Like Live On Wandbox.

<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd"> Philomena Glinka science 3 Philomena Preet alchemy 84 John Habakuk alchemy 19 Ernest Habakuk philosophy 31 John Bufgloon science 44

Simplifying

You can do without the lambdas and the make the differences between write/read go away, by using std::mem_fn:

static inline boost::dynamic_properties DynProps(Graph& g) {
    boost::dynamic_properties dp;

    dp.property("Name", get(&GraphData::Name, g));

    auto student = get(&GraphData::student, g);
    dp.property("roll_no",  make_transform_value_property_map(std::mem_fn(&Student::roll_no), 

student)); dp.property("division", make_transform_value_property_map(std::mem_fn(&Student::division), student));

    return dp;
}

Which can be used for ReadGraph and WriteGraph as follows:

Graph ReadGraph(std::string const &fileName) {
    Graph graph;
    auto dp = DynProps(graph);

    std::ifstream file(fileName);
    boost::read_graphml(file, graph, dp);

    return graph;
}

void WriteGraph(std::ostream &os, Graph &graph) {
    boost::write_graphml(os, graph, DynProps(graph), true);
}

You still get the same XML.

FULL LISTING

Live On Wandbox

#include <boost/graph/adjacency_list.hpp>
#include <boost/property_map/transform_value_property_map.hpp>
#include <boost/graph/graphml.hpp>

struct Student {
    int roll_no;
    std::string division;
};

struct GraphData {
    std::string Name;
    Student student;
};

using Graph = boost::adjacency_list<boost::setS, boost::vecS, boost::directedS, GraphData>;

static inline boost::dynamic_properties DynProps(Graph& g) {
    boost::dynamic_properties dp;

    dp.property("Name", get(&GraphData::Name, g));

    auto student = get(&GraphData::student, g);
    dp.property("roll_no",  make_transform_value_property_map(std::mem_fn(&Student::roll_no), 

student)); dp.property("division", make_transform_value_property_map(std::mem_fn(&Student::division), student));

    return dp;
}

Graph ReadGraph(std::string const &fileName) {
    Graph graph;
    auto dp = DynProps(graph);

    std::ifstream file(fileName);
    boost::read_graphml(file, graph, dp);

    return graph;
}

void WriteGraph(std::ostream &os, Graph &graph) {
    boost::write_graphml(os, graph, DynProps(graph), true);
}

void WriteGraph(std::string const& fileName, Graph &graph) {
    std::ofstream ofs(fileName);
    WriteGraph(ofs, graph);
}

#include <boost/graph/graph_utility.hpp>

namespace Gen { Graph graph(); } // generate random data

int main() {
    {
        Graph g = Gen::graph();
        WriteGraph("input.txt", g);
    }

    Graph g = ReadGraph("input.txt");
    print_graph(g, get(&GraphData::Name, g));

    // or as XML
    WriteGraph(std::cout << "==== XML version: ====\n\n", g);
}

/// generate data
#include <boost/graph/random.hpp>
#include <boost/random.hpp>
#include <random>

namespace Gen { 
    namespace {
        namespace R = boost::random;
        R::mt19937 engine {42}; // { std::random_device{}() };

        template <typename Range> auto sample(Range const &from) {
            return *std::next(boost::begin(from), R::uniform_int_distribution<>(1, boost::size(from))(engine) - 1);
        }

        int roll() { return R::uniform_int_distribution<>(1, 100)(engine); }

        std::string division() {
            static std::string const d[] = { "science", "drama", "mathematics", "philosophy", "alchemy" };
            return sample(d);
        }

        std::string name() {
            static std::string const f[] = { "John", "Daisy", "Chuck", "Mary-Anne", "Ernest", "Philomena", "Joyce", "James" };
            static std::string const l[] = { "Joyce", "Habakuk", "Hornam", "Bufgloon", "Glinka", "Tarsinck", "Preet" };
            return sample(f) + " " + sample(l);
        }

        Student student() { return { roll(), division() }; }
    }

    Graph graph() {
        Graph g;
        boost::generate_random_graph(g, 5, 7, engine);
        for (auto vd: boost::make_iterator_range(vertices(g)))
            g[vd] = { name(), student() };
        return g;
    }

} // namespace Gen

When You Have A Vector Member

Your current question adds vector to the mix. There's a related post here: read boost graph (boost::read_graphviz) where vertex contains vector

You likely ran into the ADL trap with operator<</operator>> for std::vector<T>.

In case the above doesn't already solve the issue you were having, I'll add a demo later tonight.

Demonstration

This was trickier than I'd have hoped, because overloading operator<</operator>> inside namespace ::std is just poor taste, so we need a wrapper type:

using StudentInfo = std::vector<std::pair<std::string, int>>;

struct Wrapper {
    StudentInfo& _si;

    friend std::ostream& operator<<(std::ostream& os, const Wrapper sis) {
        for(auto& pair : sis._si)
            os << std::quoted(pair.first) << pair.second << ';';
        return os;
    }

    friend std::istream& operator>>(std::istream& is, const Wrapper sis) {
        StudentInfo::value_type pair;
        while (is >> std::quoted(pair.first)) {
            char ch;
            if (is >> pair.second >> ch && ch == ';')
                sis._si.push_back(pair);
            else 
                return is;
        }
        if (!is.bad()) // eof this point is ok
            is.clear();
        return is;
    }
};

Note, below code adds ", " between name and age.

Now, some extra obstacles appear, since e.g. TransformValuePropertyMap is not an LValuePropertyMap unless the returned value is a mutable reference.

I opted to do a simple CoercePropertyMap to "wrap" a property with a a wrapping type:

template <typename T, typename Map> struct CoercePropertyMap : Map {
    CoercePropertyMap(Map map) : Map(map){}
    using value_type = T;
    using reference = T;
};

template <typename T, typename Map> 
    CoercePropertyMap<T, Map> coerce_map(Map map) { return map; }

Now, we can put it all together:

dp.property("studentInfo", coerce_map(get(&GraphItem::studentInfo, g)));

Full Listing

Live On Wandbox

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphml.hpp>
#include <iostream>
#include <iomanip>

using StudentInfo = std::vector<std::pair<std::string, int>>;

namespace /*static*/ {
    struct Lit { char ch; };
    static inline std::istream& operator>>(std::istream& is, Lit expected) {
        char actual;
        if (is >> actual && actual != expected.ch)
            is.setstate(std::ios::failbit);
        return is;
    }

    static StudentInfo null_info;

    struct Wrapper {
        StudentInfo& _si;
        Wrapper(StudentInfo& si = null_info) : _si(si) {}
        operator StudentInfo&() const { return _si; }

        friend std::ostream& operator<<(std::ostream& os, const Wrapper sis) {
            for(auto& pair : sis._si)
                os << std::quoted(pair.first) << ", " << pair.second << ';';
            return os;
        }

        friend std::istream& operator>>(std::istream& is, const Wrapper sis) {
            StudentInfo::value_type pair;

            while (is >> std::skipws >> std::quoted(pair.first)) {
                if (is >> Lit{','} >> pair.second >> Lit{';'})
                    sis._si.push_back(pair);
                else 
                    return is; // error here is bad
            }
            if (!is.bad()) // just eof this point is ok
                is.clear();
            return is;
        }
    };

    template <typename T, typename Map> struct CoercePropertyMap : Map {
        CoercePropertyMap(Map map) : Map(map){}
        using value_type = T;
        using reference = T;
    };

    template <typename T, typename Map> 
        CoercePropertyMap<T, Map> coerce_map(Map map) { return map; }
}

struct GraphItem {
    std::string Division;
    StudentInfo studentInfo;
};

using Graph = boost::adjacency_list<boost::setS, boost::vecS, boost::directedS, GraphItem>;

static inline boost::dynamic_properties DynProps(Graph& g) {
    boost::dynamic_properties dp;

    dp.property("Division", get(&GraphItem::Division, g));
    dp.property("studentInfo", coerce_map<Wrapper>(get(&GraphItem::studentInfo, g)));

    return dp;
}

Graph ReadGraph(std::string const &fileName) {
    Graph graph;
    auto dp = DynProps(graph);

    std::ifstream file(fileName);
    boost::read_graphml(file, graph, dp);

    return graph;
}

void WriteGraph(std::ostream &os, Graph &graph) {
    boost::write_graphml(os, graph, DynProps(graph), true);
}

void WriteGraph(std::string const& fileName, Graph &graph) {
    std::ofstream ofs(fileName);
    WriteGraph(ofs, graph);
}

#include <boost/graph/graph_utility.hpp>

namespace Gen { Graph graph(); } // generate random data

int main() {
    {
        Graph g = Gen::graph();
        WriteGraph("input.txt", g);
    }

    Graph g = ReadGraph("input.txt");
    print_graph(g, get(&GraphItem::Division, g));

    // or as XML
    WriteGraph(std::cout << "==== XML version: ====\n\n", g);
}

/// generate data
#include <boost/graph/random.hpp>
#include <boost/random.hpp>
#include <random>

namespace Gen { 
    namespace {
        namespace R = boost::random;
        R::mt19937 engine {42}; // { std::random_device{}() };

        template <typename Range> auto sample(Range const &from) {
            return *std::next(boost::begin(from), R::uniform_int_distribution<>(1, boost::size(from))(engine) - 1);
        }

        int age() { return R::uniform_int_distribution<>(18, 27)(engine); }

        std::string division() {
            static std::string const d[] = { "science", "drama", "mathematics", "philosophy", "alchemy" };
            return sample(d);
        }

        std::string name() {
            static std::string const f[] = { "John", "Daisy", "Chuck", "Mary-Anne", "Ernest", "Philomena", "Joyce", "James" };
            static std::string const l[] = { "Joyce", "Habakuk", "Hornam", "Bufgloon", "Glinka", "Tarsinck", "Preet" };
            return sample(f) + " " + sample(l);
        }

        StudentInfo studentInfo() {
            StudentInfo si;
            auto const n = R::uniform_int_distribution<>(2,5)(engine);
            for (int i = 0; i < n; ++i) 
                si.emplace_back(name(), age());
            return si;
        }
    }

    Graph graph() {
        Graph g;
        boost::generate_random_graph(g, 5, 7, engine);
        for (auto vd: boost::make_iterator_range(vertices(g)))
            g[vd] = { division(), studentInfo() };
        return g;
    }

} // namespace Gen

Prints

philosophy --> drama 
drama --> mathematics 
drama --> philosophy 
mathematics --> philosophy drama 
drama --> philosophy drama 
==== XML version: ====

<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
  <key id="key0" for="node" attr.name="Division" attr.type="string" />
  <key id="key1" for="node" attr.name="studentInfo" attr.type="string" />
  <graph id="G" edgedefault="directed" parse.nodeids="canonical" parse.edgeids="canonical" parse.order="nodesfirst">
    <node id="n0">
      <data key="key0">philosophy</data>
      <data key="key1">&quot;James Joyce", 18;"James Tarsinck", 25;"Daisy Joyce", 20;"Ernest Habakuk", 27;</data>
    </node>
    <node id="n1">
      <data key="key0">drama</data>
      <data key="key1">&quot;James Joyce", 18;"James Tarsinck", 25;"Daisy Joyce", 20;"Ernest Habakuk", 27;"Mary-Anne Joyce", 23;"Ernest Hornam", 18;"Daisy Hornam", 24;"James Hornam", 18;</data>
    </node>
    <node id="n2">
      <data key="key0">drama</data>
      <data key="key1">&quot;James Joyce", 18;"James Tarsinck", 25;"Daisy Joyce", 20;"Ernest Habakuk", 27;"Mary-Anne Joyce", 23;"Ernest Hornam", 18;"Daisy Hornam", 24;"James Hornam", 18;"Joyce Joyce", 22;"Mary-Anne Habakuk", 24;</data>
    </node>
    <node id="n3">
      <data key="key0">mathematics</data>
      <data key="key1">&quot;James Joyce", 18;"James Tarsinck", 25;"Daisy Joyce", 20;"Ernest Habakuk", 27;"Mary-Anne Joyce", 23;"Ernest Hornam", 18;"Daisy Hornam", 24;"James Hornam", 18;"Joyce Joyce", 22;"Mary-Anne Habakuk", 24;"John Bufgloon", 23;"Philomena Glinka", 26;"John Bufgloon", 19;"James Preet", 18;"Joyce Bufgloon", 27;</data>
    </node>
    <node id="n4">
      <data key="key0">drama</data>
      <data key="key1">&quot;James Joyce", 18;"James Tarsinck", 25;"Daisy Joyce", 20;"Ernest Habakuk", 27;"Mary-Anne Joyce", 23;"Ernest Hornam", 18;"Daisy Hornam", 24;"James Hornam", 18;"Joyce Joyce", 22;"Mary-Anne Habakuk", 24;"John Bufgloon", 23;"Philomena Glinka", 26;"John Bufgloon", 19;"James Preet", 18;"Joyce Bufgloon", 27;"Daisy Joyce", 18;"Mary-Anne Habakuk", 24;"Ernest Joyce", 24;</data>
    </node>
    <edge id="e0" source="n0" target="n2">
    </edge>
    <edge id="e1" source="n1" target="n3">
    </edge>
    <edge id="e2" source="n2" target="n0">
    </edge>
    <edge id="e3" source="n3" target="n0">
    </edge>
    <edge id="e4" source="n3" target="n2">
    </edge>
    <edge id="e5" source="n4" target="n0">
    </edge>
    <edge id="e6" source="n4" target="n1">
    </edge>
  </graph>
</graphml>
sehe
  • 374,641
  • 47
  • 450
  • 633
  • I gave you the explanation I had prepared, and the quick hints I could give right now. Later tonight I can expand with a demonstration. – sehe Mar 07 '18 at 17:54
  • Added that demonstration (also [Live on Coliru](http://coliru.stacked-crooked.com/a/1ece81b22b13253a)). It was a little bit tricky for reasons explained in the answer. – sehe Mar 08 '18 at 00:29