Boosts JSON parser escapes quotation marks on MacOS but not on Linux - c++

I have a C++ program running on a server returning JSON formatted data, serialized with boost. In some cases one value part of a JSON packet is another JSON packet serialized to string. Running in XCode on my developing machine the program returns me the value data with escaped quotation marks, compiled on the Linux system without. Is there any way to force boost on linux to do that or a good workaround? (There was an error with missing value quotation marks in general that we already fixed).
Beneath is as an example the first line:
local:
"stations": "{\n \"area CHALLENGE_FUTURE\": \"{\\n \\\"areaname CHALLENGE_FUTURE\\\": [...]"
online:
"stations": "{\n "area CHALLENGE_FUTURE": "{\\n "areaname CHALLENGE_FUTURE": [...]"
Boost version is 1.57, compiler (Local Xcode and Online Linux) is clang-linux-3.5. (see edit)
Here's the simplified code:
// ----- STL
#include <iostream>
#include <string>
#include <map>
// ------ boost
#include <boost/asio.hpp>
#include <boost/foreach.hpp>
//formatter_base.hpp
//------------------------------------------------------------------------------
class formatter_base
{
protected:
std::map<std::string, std::string> datas;
public:
virtual ~formatter_base() {}
void setvalue(std::string key, std::string value)
{
datas[key] = value;
}
std::string getvalue(std::string key)
{
return datas[key];
}
bool containsKey(std::string key)
{
return (datas.find(key) != datas.end());
}
virtual void deserialize(char *data, const std::size_t size) = 0;
virtual std::string serialize(std::vector<std::string> keys) = 0;
};
//json_formatter.hpp
class json_formatter : public formatter_base
{
public:
virtual void deserialize(char *data, const std::size_t size);
virtual std::string serialize(std::vector<std::string> keys);
};
//datapacket.hpp
//------------------------------------------------------------------------------
class server;
extern server *tcp_server;
class datapacket
{
static const char id[4];
public:
enum DataFormat { BINARY = 0, JSON, XML };
std::string ip;
bool useHeader;
datapacket() : useHeader(false), packet_data(NULL) {}
datapacket(DataFormat format);
std::vector<char> process(char *data, std::size_t size, std::string ip);
std::string getvalue(std::string key)
{
return packet_data->getvalue(key);
}
void setvalue(std::string key, std::string value)
{
packet_data->setvalue(key, value);
}
bool containsKey(std::string key)
{
return packet_data->containsKey(key);
}
std::vector<char> serialize();
std::string toString();
private:
bool deserialize(char *data, std::size_t size);
std::string serialize_data(std::vector<std::string> keys);
formatter_base *packet_data;
};
//datapacket.cpp
//------------------------------------------------------------------------------
#include <boost/iostreams/stream.hpp>
#include <boost/foreach.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <string.h>
datapacket::datapacket(DataFormat format)
: useHeader(false)
{
if(format == JSON)
{
packet_data = new json_formatter();
}
else
{
packet_data = NULL;
}
}
std::vector<char> datapacket::process(
char *data, std::size_t size, std::string ip)
{
//std::cout << "datapacket::process" << std::endl;
this->ip = ip;
std::vector<char> ret;
if (!deserialize(data, size))
return ret;
std::vector<std::string> keys;
std::string result;
/*extern void process(datapacket& gamedata);
process(*this);*/
ret.push_back('a');
ret.push_back('c');
ret.push_back('k');
return ret;
}
bool datapacket::deserialize(char *data, std::size_t size)
{
packet_data = new json_formatter();
packet_data->deserialize(data, size);
return true;
}
std::string datapacket::serialize_data(std::vector<std::string> keys)
{
return packet_data->serialize(keys);
}
std::vector<char> datapacket::serialize()
{
std::vector<std::string> keys;
std::string str = serialize_data(keys);
std::vector<char> ret;
ret.assign(str.begin(), str.end());
return ret;
}
std::string datapacket::toString()
{
std::vector<std::string> keys;
std::string str = serialize_data(keys);
return str;
}
//json_formatter.cpp
//------------------------------------------------------------------------------
using namespace boost::property_tree;
void json_formatter::deserialize(char *data, const std::size_t size)
{
std::stringstream ss;
ss.write(data, size);
// std::cout << "ss: " << ss.str() << std::endl;
ptree pt;
read_json(ss, pt);
BOOST_FOREACH(ptree::value_type &v, pt)
{
//log all received data
//std::cout << v.first.data() << ": " << v.second.data() << std::endl;
datas[v.first.data()] = v.second.data();
}
}
///-workaround 1.57 json
template <typename T>
struct my_id_translator
{
typedef T internal_type;
typedef T external_type;
boost::optional<T> get_value(const T &v) { return v.substr(1, v.size() - 2) ; }
boost::optional<T> put_value(const T &v) { return '"' + v + '"'; }
};
///
std::string json_formatter::serialize(std::vector<std::string> keys)
{
ptree pt;
if(keys.empty())
{
typedef std::map<std::string, std::string> mapType;
BOOST_FOREACH(const mapType::value_type& myPair, datas)
{
//workaround for wrong formatted string
if((BOOST_VERSION == 105700) && (BOOST_OS_LINUX))
{
//1.57
pt.put(myPair.first, myPair.second, my_id_translator<std::string>());
}
else
{
//1.54
pt.put(myPair.first, myPair.second);
}
//std::cout << myPair.first << ": " << myPair.second << std::endl;
}
}
else
{
BOOST_FOREACH(std::string key, keys)
{
//workaround for wrong formatted string
if(BOOST_VERSION == 105700)
{
#if BOOST_OS_LINUX
pt.put(key, "\"" + datas[key] + "\"", my_id_translator<std::string>());
#else
pt.put(key, datas[key], my_id_translator<std::string>());
#endif
}
else
{
pt.put(key, datas[key]);
}
// std::cout << key << ": " << datas[key] << std::endl;
}
}
std::stringstream ss;
write_json(ss, pt);
std::string str = ss.str();
// WORKAROUND
// Replace all escaped backslashes
// This was because some clients couldn't interpret "command\\/read"
std::string oldStr = "\\/";
std::string newStr = "/";
std::size_t pos = 0;
while((pos = str.find(oldStr)) != std::string::npos){
str = str.replace(pos, oldStr.length(), newStr);
pos += newStr.length();
}
// /WORKAROUND
//std::cout << "Serialize message:" << std::endl;
//std::cout << str << std::endl;
return str;
}
//main.cpp
//------------------------------------------------------------------------------
class dataClass
{
public:
dataClass() {}
std::string name;
};
class innerDataClass
{
public:
innerDataClass() {}
std::string name;
int score;
std::string baseClassName;
};
using boost::asio::ip::tcp;
namespace stdpatch
{
template <typename T> std::string to_string(const T& n)
{
std::ostringstream stm;
stm << n;
return stm.str();
}
}
std::map<std::string, dataClass> listDC;
std::map<std::string, innerDataClass> listIDC;
void Init()
{
//Some initial values
dataClass d1; d1.name = "dataClass1"; listDC["d1"] = d1;
dataClass d2; d2.name = "dataClass2"; listDC["d2"] = d2;
innerDataClass i1; i1.name = "innerClass1"; i1.baseClassName = "dataClass1";
i1.score = 5; listIDC["i1"] = i1;
innerDataClass i2; i2.name = "innerClass2"; i2.baseClassName = "dataClass1";
i1.score = 21; listIDC["i2"] = i2;
innerDataClass i3; i3.name = "innerClass3"; i1.baseClassName = "dataClass2";
i1.score = 1; listIDC["i3"] = i3;
}
//returns JSON
datapacket GetJSON()
{
std::pair<std::string, dataClass> baseClassPair;
std::pair<std::string, innerDataClass> innerClassPair;
datapacket baseClasses (datapacket::JSON);
baseClasses.setvalue("comment", "this holds all the base classes");
BOOST_FOREACH(baseClassPair, listDC)
{
datapacket baseClassData (datapacket::JSON);
baseClassData.setvalue("dataName", baseClassPair.first);
BOOST_FOREACH(innerClassPair, listIDC)
{
if (innerClassPair.second.baseClassName == baseClassPair.second.name)
{
datapacket innerClassData (datapacket::JSON);
innerClassData.setvalue(
"name", innerClassPair.second.name);
innerClassData.setvalue(
"score", stdpatch::to_string(innerClassPair.second.score));
baseClassData.setvalue(
"inner " + innerClassPair.first, innerClassData.toString());
}
}
baseClasses.setvalue("base " + baseClassPair.first, baseClassData.toString());
}
datapacket packet (datapacket::JSON);
packet.setvalue("comment", "this is the base-packet");
packet.setvalue("baseClasses", baseClasses.toString());
return packet;
}
int main(int argc, char* argv[])
{
Init();
datapacket packet (datapacket::JSON);
packet = GetJSON();
std::cout << std::endl << std::endl
<< "------------- RESULT --------------"
<< std::endl << std::endl;
std::cout << packet.toString() << std::endl;
return 0;
}
The expected output should be:
------------- RESULT --------------
baseClasses: {
"base d1": "{\n \"dataName\": \"d1\",\n \"inner i1\": \"{\\n \\\"gameID\\\": \\\"5\\\",\\n \\\"name\\\": \\\"innerClass1\\\"\\n}\\n\",\n \"inner i2\": \"{\\n \\\"gameID\\\": \\\"1989860191\\\",\\n \\\"name\\\": \\\"innerClass2\\\"\\n}\\n\"\n}\n",
"base d2": "{\n \"dataName\": \"d2\"\n}\n",
"comment": "this holds all the base classes"
}
comment: this is the base-packet
{
"baseClasses": "{\n \"base d1\": \"{\\n \\\"dataName\\\": \\\"d1\\\",\\n \\\"inner i1\\\": \\\"{\\\\n \\\\\\\"gameID\\\\\\\": \\\\\\\"5\\\\\\\",\\\\n \\\\\\\"name\\\\\\\": \\\\\\\"innerClass1\\\\\\\"\\\\n}\\\\n\\\",\\n \\\"inner i2\\\": \\\"{\\\\n \\\\\\\"gameID\\\\\\\": \\\\\\\"1989860191\\\\\\\",\\\\n \\\\\\\"name\\\\\\\": \\\\\\\"innerClass2\\\\\\\"\\\\n}\\\\n\\\"\\n}\\n\",\n \"base d2\": \"{\\n \\\"dataName\\\": \\\"d2\\\"\\n}\\n\",\n \"comment\": \"this holds all the base classes\"\n}\n",
"comment": "this is the base-packet"
}
but the quotation marks are unescaped in my servers case:
comment: this is the base-packet
{
"baseClasses": "{\n "base d1": "{\\n "dataName": "d1",\\n "inner i1": "{\\\\n "gameID": "5",\\\\n "name": "innerClass1"\\\\n}\\\\n",\\n "inner i2": "{\\\\n "gameID": "1989860191",\\\\n "name": "innerClass2"\\\\n}\\\\n"\\n}\\n",\n "base d2": "{\\n \\\"dataName": "d2"\\n}\\n",\n "comment": "this holds all the base classes"\n}\n",
"comment": "this is the base-packet"
}
To test wether the problem is within write_json or the transit I made a simple JSON package:
datapacket testData(datapacket::JSON);
testData.setvalue("text", "\"world\"");
testData.setvalue("inner1", testData.toString());
testData.setvalue("inner2", testData.toString());
The results are the following:
XCode:
{
"inner1": "{\n \"text\": \"\\\"world\\\"\"\n}\n",
"inner2": "{\n \"inner1\": \"{\\n \\\"text\\\": \\\"\\\\\\\"world\\\\\\\"\\\"\\n}\\n\",\n \"text\": \"\\\"world\\\"\"\n}\n",
"text": "\"world\""
}
Server:
{
"inner1": "{\n "text": ""world""\n}\n"
"inner2": "{\n "text": ""world"",\n "inner1": "{\\n "text": ""world""\\n}\\n"\n}\n",
"text": ""world""
}
This should show that the problem is within the write_json

On my linux box I've tested this to be not an issue:
Live On Coliru
#include<boost/property_tree/json_parser.hpp>
#include<iostream>
int main(){
using namespace boost::property_tree;
ptree inner;
inner.put("area CHALLENGE_FUTURE.areaname CHALLENGE_FUTURE", "something");
std::ostringstream inner_json;
write_json(inner_json, inner);
ptree outer;
outer.put("stations", inner_json.str());
write_json(std::cout, outer);
}
Which prints
{
"stations": "{\n \"area CHALLENGE_FUTURE\": {\n \"areaname CHALLENGE_FUTURE\": \"something\"\n }\n}\n"
}
Exactly what it should print if you ask me.
If you want the 'inner' JSON to be unescaped, why not make it part of the same tree?
Live On Coliru
#include<boost/property_tree/json_parser.hpp>
#include<iostream>
int main(){
using namespace boost::property_tree;
ptree outer;
auto& inner = outer.add_child("stations", {});
inner.put("area CHALLENGE_FUTURE.areaname CHALLENGE_FUTURE", "something");
write_json(std::cout, outer);
}
Which prints
{
"stations": {
"area CHALLENGE_FUTURE": {
"areaname CHALLENGE_FUTURE": "something"
}
}
}

This might be the silliest error ever: After weeks of tracing the error down, we've found out that the linux compiling shell script we've used to build the project on our machine manually changed the JSON parsing behaviour.
If for all we've learned something from this, it is: Never trust stuff you've copied from the internet.

Related

How create a JSON-file from my data? (with С++, boost json)

I wanted to create a Json-file that will be created from the data received earlier. I don't understand how to work with Json files at all. I want to use the Boost library, because I am using it in another part of this program. I need to create a Json-file with a specific structure which I have attached below.
I need to get JSON:
{
"track": {
"Wheels": {
"Wheel": [
{
"start_pos": "10",
"end_pos": "25"
},
{
"start_pos": "22",
"end_pos": "78"
}
]
},
"Brakes": {
"Brake": [
{
"start_pos": "10",
"midl_pos": "25"
}
]
}
}
}
C++:
#include "boost/property_tree/ptree.hpp"
#include "boost/property_tree/json_parser.hpp"
#include <string>
using namespace std;
using boost::property_tree::ptree;
struct wheel
{
string start_pos;
string end_pos;
};
struct brake
{
string start_pos;
string midl_pos;
};
int main()
{
string tr = "track";
string ws = "Wheels";
string bs = "Brakes";
struct wheel w1;
w1.start_pos = "10";
w1.end_pos = "25";
struct wheel w2;
w2.start_pos = "22";
w2.end_pos = "78";
struct brake b1;
b1.start_pos = "10";
b1.midl_pos = "25";
return 0;
}
Implementing it with the Boost JSON customization points.
Doing it test-driven:
Live On Coliru
#include "boost/json/src.hpp" // header-only approach
#include <iostream>
namespace json = boost::json;
using json::value_from;
using json::value_to;
static const json::value expected = json::parse(R"({
"track": {
"Wheels": {
"Wheel": [
{
"start_pos": "10",
"end_pos": "25"
},
{
"start_pos": "22",
"end_pos": "78"
}
]
},
"Brakes": {
"Brake": [
{
"start_pos": "10",
"midl_pos": "25"
}
]
}
}
})");
namespace MyLib {
struct wheel { int start_pos, end_pos; };
struct brake { int start_pos, midl_pos; };
struct track {
std::vector<wheel> wheels;
std::vector<brake> brakes;
};
void tag_invoke(json::value_from_tag, json::value& jv, wheel const& w) {
jv = {
{"start_pos", std::to_string(w.start_pos)},
{"end_pos", std::to_string(w.end_pos)},
};
}
void tag_invoke(json::value_from_tag, json::value& jv, brake const& b) {
jv = {
{"start_pos", std::to_string(b.start_pos)},
{"midl_pos", std::to_string(b.midl_pos)},
};
}
void tag_invoke(json::value_from_tag, json::value& jv, track const& t) {
jv = {{"track",
{
{"Wheels", {{"Wheel", t.wheels}}},
{"Brakes", {{"Brake", t.brakes}}},
}}};
}
}
int main()
{
MyLib::track track{{
{10, 25},
{22, 78},
},
{
{10, 25},
}};
json::value output = json::value_from(track);
std::cout << output << "\n";
std::cout << expected << "\n";
std::cout << "matching: " << std::boolalpha << (output == expected) << "\n";
}
Prints
{"track":{"Wheels":{"Wheel":[{"start_pos":"10","end_pos":"25"},{"start_pos":"22","end_pos":"78"}]},"Brakes":{"Brake":[{"start_pos":"10","midl_pos":"25"}]}}}
{"track":{"Wheels":{"Wheel":[{"start_pos":"10","end_pos":"25"},{"start_pos":"22","end_pos":"78"}]},"Brakes":{"Brake":[{"start_pos":"10","midl_pos":"25"}]}}}
matching: true
BONUS
Adding full round-trip support. I elected to remove the to_string as it looks like it may have been a requirement from the Boost Property Tree limitations only:
Live On Coliru
#include "boost/json/src.hpp" // header-only approach
#include <iostream>
namespace json = boost::json;
using json::value_from;
using json::value_to;
namespace MyLib {
struct wheel {
int start_pos, end_pos;
bool operator==(wheel const&) const = default;
};
struct brake {
int start_pos, midl_pos;
bool operator==(brake const&) const = default;
};
struct track {
std::vector<wheel> wheels;
std::vector<brake> brakes;
bool operator==(track const&) const = default;
};
void tag_invoke(json::value_from_tag, json::value& jv, wheel const& w) {
jv = {{"start_pos", w.start_pos}, {"end_pos", w.end_pos}};
}
void tag_invoke(json::value_from_tag, json::value& jv, brake const& b) {
jv = {{"start_pos", b.start_pos}, {"midl_pos", b.midl_pos}};
}
void tag_invoke(json::value_from_tag, json::value& jv, track const& t) {
jv = {{"track",
{
{"Wheels", {{"Wheel", t.wheels}}},
{"Brakes", {{"Brake", t.brakes}}},
}}};
}
wheel tag_invoke(json::value_to_tag<wheel>, json::value const& jv) {
return {
value_to<int>(jv.at("start_pos")),
value_to<int>(jv.at("end_pos")),
};
}
brake tag_invoke(json::value_to_tag<brake>, json::value const& jv) {
return {
value_to<int>(jv.at("start_pos")),
value_to<int>(jv.at("midl_pos")),
};
}
track tag_invoke(json::value_to_tag<track>, json::value const& jv) {
auto& track = jv.at("track");
return {
value_to<decltype(track::wheels)>(track.at("Wheels").at("Wheel")),
value_to<decltype(track::brakes)>(track.at("Brakes").at("Brake")),
};
}
}
int main()
{
MyLib::track const track{{
{110, 125},
{111, 126},
{142, 198},
},
{
{10, 25},
{120, 135},
}};
json::value output = json::value_from(track);
std::cout << output << "\n";
std::cout << "Roundtrip: " << std::boolalpha
<< (value_to<MyLib::track>(output) == track) << "\n";
}
Prints
{"track":{"Wheels":{"Wheel":[{"start_pos":110,"end_pos":125},{"start_pos":111,"end_pos":126},{"start_pos":142,"end_pos":198}]},"Brakes":{"Brake":[{"start_pos":10,"midl_pos":25},{"start_pos":120,"midl_pos":135}]}}}
Roundtrip: true
Despite comments suggesting to use a different library, which they could, I think #Nindzzya wanted to specifically use the boost library.
Using the boost library from How to use boost::property_tree to load and write JSON:
#include "boost/property_tree/ptree.hpp"
#include "boost/property_tree/json_parser.hpp"
#include <string>
#include <iostream>
using namespace std;
namespace pt = boost::property_tree;
struct wheel
{
string start_pos;
string end_pos;
};
struct brake
{
string start_pos;
string midl_pos;
};
int main()
{
string tr = "track";
string ws = "Wheels";
string bs = "Brakes";
struct wheel w1;
w1.start_pos = "10";
w1.end_pos = "25";
struct wheel w2;
w2.start_pos = "22";
w2.end_pos = "78";
struct brake b1;
b1.start_pos = "10";
b1.midl_pos = "25";
pt::ptree wheel1, wheel2, wheels, wheel;
pt::ptree brake1, brakes, brake;
pt::ptree track, root;
wheel1.put("start_pos", w1.start_pos);
wheel1.put("end_pos", w1.end_pos);
wheel2.put("start_pos", w2.start_pos);
wheel2.put("end_pos", w2.end_pos);
wheels.push_back(make_pair("", wheel1));
wheels.push_back(make_pair("", wheel2));
wheel.add_child("Wheel", wheels);
track.add_child(ws, wheel);
brake1.put("start_pos", b1.start_pos);
brake1.put("midl_pos", b1.midl_pos);
brakes.push_back(make_pair("", brake1));
brake.add_child("Brake", brakes);
track.add_child(bs, brake);
root.add_child(tr, track);
pt::write_json(std::cout, root);
return 0;
}
results in:
{
"track": {
"Wheels": {
"Wheel": [
{
"start_pos": "10",
"end_pos": "25"
},
{
"start_pos": "22",
"end_pos": "78"
}
]
},
"Brakes": {
"Brake": [
{
"start_pos": "10",
"midl_pos": "25"
}
]
}
}
}

Creating std::set copies only one element, how to fix this?

v_map has the correct amount of information stored, however when i try to use std::set it only copies one element ,I assume the first one. This is my first time using std::set , maybe I miss something here...Thanks for your help !
typedef std::map<std::string,std::pair<int,int>> points_map;
void list_average(points_map &v_map)
{
Comparator compFunctor = [](std::pair<std::string,std::pair<int,int>> elem1,std::pair<std::string,std::pair<int,int>> elem2)
{
std::pair<int,int> it = elem1.second;
std::pair<int,int> jt = elem2.second;
return it.first < jt.first;
};
std::set<std::pair<std::string,std::pair<int,int>>,Comparator> v_set(v_map.begin(),v_map.end(),compFunctor);
for (std::pair<std::string,std::pair<int,int>> it : v_set)
{
std::pair<int,int> jt = it.second;
std::cout << it.first << " " << (jt.second - jt.first) / jt.first<< std::endl;
}
}
Note the following is the full program, I apologize in advance for the ugly code , and length of the code ,also I rewrote the name in the upper part of my code, in the full code , this particular function is called list_atlag
#include <iostream>
#include <string>
#include <map>
#include <set>
#include <vector>
#include <codecvt>
#include <iterator>
#include <numeric>
#include <functional>
#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
#include <boost/program_options.hpp>
#include <boost/tokenizer.hpp>
class Adatok
{
public:
Adatok(std::string name, std::string path, std::string date, int points) : _name(name), _path(path), _date(date), _points(points) {}
Adatok(const Adatok &other) = default;
Adatok &operator=(const Adatok &other) = default;
std::string get_name() { return _name; }
std::string get_path() { return _path; }
std::string get_date() { return _date; }
int get_points() { return _points; }
private:
std::string _name;
std::string _path;
std::string _date;
int _points;
};
class Ranglista
{
public:
Ranglista(std::string name, int points) : _name(name), _points(points) {}
Ranglista(const Ranglista &other) = default;
Ranglista &operator=(const Ranglista &other) = default;
std::string get_name() { return _name; }
int get_points() { return _points; }
bool operator<(const Ranglista &other)
{
return _points > other._points;
}
private:
std::string _name;
int _points;
};
class Vedes
{
public:
Vedes(std::string name, int point) : _name(name), _point(point) { _count++; }
Vedes(const Vedes &other) = default;
Vedes &operator=(const Vedes &other) = default;
std::string get_name() { return _name; }
int get_point() { return _point; }
int get_count() { return _count; }
void set_stuff(int &points)
{
_point += points;
_count++;
}
bool operator<(const Vedes &other)
{
return _count > other._count;
}
private:
std::string _name;
int _point;
int _count = 0;
};
typedef std::map<std::string, int> path_value; //minden path + az erteke
typedef std::vector<Adatok> name_path_date; //bejegyzesek
typedef std::vector<Ranglista> ranglista; //ranglista
typedef std::map<std::string,std::pair<int,int>> vedes_vec; //vedesek
typedef std::function<bool(std::pair<std::string,std::pair<int,int>>,std::pair<std::string,std::pair<int,int>>)> Comparator;
void create_pv(path_value &, boost::filesystem::path); //feltolti a path+ertek map-ot
void create_npd(name_path_date &, path_value &, std::string input); //feltolti a bejegyzesek vektorat + mindenki pontszama map
void create_np(name_path_date &, path_value &); // name + path map
void list_np(path_value &name_point); // nam + path kiiratas
void list_bejegyzesek(name_path_date &bejegyzesek); // bejegyzesek vektora kiiratas
bool check_bejegyzesek(name_path_date &bejegyzesek, std::string name, std::string path); //van-e mar ilyen bejegyzes
void create_rl(ranglista &rl_vec, path_value &name_point); //ranglista feltoltes
void list_rl(ranglista &rl_vec); //ranglista kiiratas
void vedes_atlag(name_path_date &bejegyzesek, vedes_vec &v_vec); //vedes atlag map
void list_atlag(vedes_vec &v_vec); //vedes atlag kiiratas
bool check_vedes(vedes_vec &v_vec, std::string name);
void vedes_elem(vedes_vec &v_vec, std::string name, int &&points); //
//void accumulate_pv(path_value&);
int main(int argc, char **argv)
{
std::vector<std::string> roots = {"City/Debrecen/Oktatás/Informatika/Programozás/DEIK/Prog1/", "City/Debrecen/Oktatás/Informatika/Programozás/DEIK/"};
std::string input_file_name = "db-2018-05-06.csv";
/* OPTIONS */
boost::program_options::options_description desc("ALLOWED OPTIONS");
desc.add_options()("help", "help msg")("root,r", boost::program_options::value<std::vector<std::string>>())("csv", boost::program_options::value<std::string>(), "comma separated values")("rank", "rang lista")("vedes", "labor vedesek");
boost::program_options::positional_options_description pdesc;
pdesc.add("root", -1);
boost::program_options::variables_map vm;
boost::program_options::store(boost::program_options::command_line_parser(argc, argv).options(desc).positional(pdesc).run(), vm);
boost::program_options::notify(vm);
int sum = 0;
path_value pv_map;
if (vm.count("help") || argc == 1)
{
std::cout << desc << std::endl;
return 1;
}
if (vm.count("root"))
{
roots = vm["root"].as<std::vector<std::string>>();
for (auto &i : roots)
{
boost::filesystem::path path(i);
create_pv(pv_map, path);
}
for (path_value::iterator it{pv_map.begin()}; it != pv_map.end(); it++)
sum += it->second;
//std::cout << sum << std::endl;create_npd
std::cout << std::accumulate(pv_map.begin(), pv_map.end(), 0, [](int value, const std::map<std::string, int>::value_type &p) { return value + p.second; });
std::cout << std::endl;
}
if (vm.count("csv"))
{
//input_file_name = vm["csv"].as<std::string>();
std::ifstream input_file{vm["csv"].as<std::string>()};
name_path_date bejegyzesek;
std::string temp;
path_value name_point;
while (getline(input_file, temp))
create_npd(bejegyzesek, pv_map, temp);
create_np(bejegyzesek, name_point);
//list_bejegyzesek(bejegyzesek);
//list_np(name_point);
if (vm.count("rank"))
{
ranglista rl_vec;
create_rl(rl_vec, name_point);
list_rl(rl_vec);
}
if (vm.count("vedes"))
{
vedes_vec v_vec;
vedes_atlag(bejegyzesek, v_vec);
list_atlag(v_vec);
}
return 0;
}
return 0;
}
void create_pv(path_value &pv_map, boost::filesystem::path path)
{
boost::filesystem::directory_iterator it{path}, eod;
BOOST_FOREACH (boost::filesystem::path const &p, std::make_pair(it, eod))
{
if (boost::filesystem::is_regular_file(p))
{
boost::filesystem::ifstream regular_file{p};
std::string temp;
int sum = 0; //aktualis .props erteke
while (getline(regular_file, temp))
{
temp.erase(0, temp.find_last_of('/'));
temp.erase(0, temp.find_first_of(' '));
sum += std::atoi((temp.substr(temp.find_first_of("0123456789"), temp.find_last_of("0123456789"))).c_str());
}
std::string result = p.string();
std::string result_path = result.substr(0, result.find_last_of('/'));
//std::cout << result_path << std::endl;
//pv_map.insert(std::make_pair(result, sum));
pv_map[result_path] = sum;
}
else
create_pv(pv_map, p);
}
}
//void accumulate_pv(path_value& pv_map)
//{
// std::cout<<std::accumulate(pv_map.begin(),pv_map.end(),0,[](int value,const path_value::int& p){return value+p.second;});
//}
void create_npd(name_path_date &bejegyzesek, path_value &pv_map, std::string input)
{
boost::tokenizer<boost::escaped_list_separator<char>> tokenizer{input};
boost::tokenizer<boost::escaped_list_separator<char>>::iterator it{tokenizer.begin()};
std::string name = *it;
std::string path = *(++it);
std::string date = *(++it);
path = path.substr(2);
if (!check_bejegyzesek(bejegyzesek, name, path))
bejegyzesek.push_back(Adatok(name, path, date, pv_map["/home/erik/Documents/Programs/"+path]));
}
bool check_bejegyzesek(name_path_date &bejegyzesek, std::string name, std::string path)
{
bool ok = false;
for (name_path_date::iterator it{bejegyzesek.begin()}; it != bejegyzesek.end(); it++)
{
if ((it->get_name() == name) && (it->get_path() == path))
ok = true;
}
return ok;
}
bool check_vedes(vedes_vec &v_vec, std::string name)
{
vedes_vec::iterator it = v_vec.find(name);
if (it != v_vec.end()) return true;
else return false;
}
void vedes_elem(vedes_vec &v_vec, std::string name, int &&points)
{
/*for (auto &it : v_vec)
if (it.get_name() == name)
it.set_stuff(points);
*/
vedes_vec::iterator i = v_vec.find(name);
std::pair<int,int> it = i->second;
//auto& jt = it->second;
it.first++;
it.second += points;
}
void create_np(name_path_date &bejegyzesek, path_value &name_point)
{
for (name_path_date::iterator it{bejegyzesek.begin()}; it != bejegyzesek.end(); it++)
if (name_point.count(it->get_name()) == 0)
name_point.insert(std::make_pair(it->get_name(), it->get_points()));
else
name_point[it->get_name()] += it->get_points();
}
void list_np(path_value &name_point)
{
for (path_value::iterator it{name_point.begin()}; it != name_point.end(); it++)
{
if (it->second)
std::cout << it->first << " " << it->second << std::endl;
}
}
void list_bejegyzesek(name_path_date &bejegyzesek)
{
for (name_path_date::iterator it{bejegyzesek.begin()}; it != bejegyzesek.end(); it++)
if (it->get_name() == "Varga Erik")
std::cout << it->get_name() << " " << it->get_path() << " " << it->get_points() << std::endl;
}
void create_rl(ranglista &rl_vec, path_value &name_point)
{
for (auto &it : name_point)
{
if (it.second > 0)
rl_vec.push_back(Ranglista(it.first, it.second));
}
std::sort(rl_vec.begin(), rl_vec.end());
}
void list_rl(ranglista &rl_vec)
{
for (auto &it : rl_vec)
std::cout << it.get_name() << " " << it.get_points() << std::endl;
}
void vedes_atlag(name_path_date &bejegyzesek, vedes_vec &v_vec)
{
std::string key = "City/Debrecen/Oktatás/Informatika/Programozás/DEIK/Prog1/Labor/Védés/";
for (auto &it : bejegyzesek)
{
if ((it.get_path().find("City/Debrecen/Oktatás/Informatika/Programozás/DEIK/Prog1/Labor/Védés/") != std::string::npos) && (it.get_points()) && (!check_vedes(v_vec, it.get_name())))
v_vec.insert(std::make_pair(it.get_name(),std::make_pair(1,it.get_points())));
else if ((check_vedes(v_vec, it.get_name())) && (it.get_path().find("City/Debrecen/Oktatás/Informatika/Programozás/DEIK/Prog1/Labor/Védés/") != std::string::npos) && (it.get_points()))
vedes_elem(v_vec, it.get_name(), it.get_points());
}
}
void list_atlag(vedes_vec &v_vec)
{
//std::sort(v_vec.begin(), v_vec.end());
Comparator compFunctor = [](std::pair<std::string,std::pair<int,int>> elem1,std::pair<std::string,std::pair<int,int>> elem2)
{
std::pair<int,int> it = elem1.second;
std::pair<int,int> jt = elem2.second;
return it.first < jt.first;
};
std::set<std::pair<std::string,std::pair<int,int>>,Comparator> v_set(v_vec.begin(),v_vec.end(),compFunctor);
//int sum = 0;
//int csum = 0;
for (std::pair<std::string,std::pair<int,int>> it : v_set)
{
std::pair<int,int> jt = it.second;
std::cout << it.first << " " << (jt.second - jt.first) / jt.first<< std::endl;
//sum += it.get_point();
//csum += it.get_count();
//sum = std::accumulate(v_vec.begin(), v_vec.end(), 0, [](int i, Vedes &o) { return i + o.get_point(); });
//csum = std::accumulate(v_vec.begin(), v_vec.end(), 0, [](int i, Vedes &o) { return i + o.get_count(); });
}
//std::cout << (sum - csum) / csum << std::endl;
}
so, as described here
template<
class Key,
class Compare = std::less<Key>,
class Allocator = std::allocator<Key>
> class set;
std::set is an associative container that contains a sorted set of unique objects of type Key.
I cleaned up your code, and made a Minimal, Complete, and Verifiable example,
#include <iostream>
#include <map>
#include <set>
using point_pair = std::pair<int,int>;
using points_map = std::map<std::string, point_pair>;
using points_set_pair = std::pair<std::string, point_pair>;
auto compFunctor = [](const points_set_pair &elem1, const points_set_pair &elem2)
{
return elem1.second.first < elem2.second.first;
};
using points_set = std::set<points_set_pair, decltype(compFunctor)>;
void list_average(const points_map &v_map)
{
points_set v_set(v_map.begin(),v_map.end(),compFunctor);
for (auto &elem : v_set)
{
const point_pair &jt = elem.second;
std::cout << elem.first << " " << (jt.second - jt.first) / jt.first<< "\n";
}
}
Now consider the first version of main
int main()
{
points_map v_map = { {"foo", { 1, 2}}, {"bar", { 3, 4}}};
list_average(v_map);
}
output:
foo 1
bar 0
Now consider the second version of main:
int main()
{
points_map v_map = { {"foo", { 1, 2}}, {"bar", { 1, 4}}};
list_average(v_map);
}
output:
bar 3
See the problem? As .second.first of the elements are both 1, the latter replaces the first. It is not unique. That's the downside of std::set.
So, what then?
Don't use std::set, but use std::vector and std::sort. Example:
#include <iostream>
#include <map>
#include <vector>
#include <algorithm>
using point_pair = std::pair<int,int>;
using points_map = std::map<std::string, point_pair>;
using string_point_pair = std::pair<std::string, point_pair>;
auto compFunctor = [](string_point_pair const &elem1, string_point_pair const &elem2)
{
return
elem1.second.first != elem2.second.first?
elem1.second.first < elem2.second.first:
elem1.second.second < elem2.second.second;
};
void list_average(points_map const &v_map)
{
std::vector<string_point_pair> v_vec(v_map.begin(),v_map.end());
std::sort(v_vec.begin(), v_vec.end(), compFunctor);
for (auto &elem : v_vec)
{
const point_pair &jt = elem.second;
std::cout << elem.first << " " << (jt.second - jt.first) / jt.first<< "\n";
}
}
int main()
{
points_map v_map = { {"foo", { 1, 2}}, {"bar", { 1, 4}}, {"baz", { 2, 4}}};
list_average(v_map);
}
Output:
foo 1
bar 3
baz 1
live demo

Parsing xml using Boost c++ for nested tags

I am trying to parse an xml file using boost which is of the following structure:
<root>
<chocos>
<choco>
<color>1</color>
<companies>
<comapany>10</company>
<comapany>11</company>
</companies>
</choco>
<choco>
<color>2</color>
<companies>
<comapany>12</company>
<comapany>13</company>
</companies>
</choco>
</chocos>
</root>
I need to store it in the following class:
class root_info
{
public:
//some members
std::vector<choco_info> chocos;
}
class choco_info
{
int color;
int companies[100];
}
I have written the following code, but it shows error :
No such node (companies)Failed to read xml file>
Code:
for(const auto& v : pt.get_child("root.chocos"))
{
choco_info choco;
if(v.first == "choco")
{
choco.color = v.second.get<unsigned>("color");
int i = 0;
for(const auto& v2 : pt.get_child("companies"))
{
if( v2.first == "company")
{
choco.company[i] = v2.second.get<unsigned>("company");
i++;
}
}
}
root.chocos.push_back(choco);
}
The xml cannot be changed as it has been given to me. The main problem lies in how to iterate through "chocos" as well as "companies"
Structuring your code helps a lot in keeping the complexity down:
Live On Coliru
#include <boost/property_tree/xml_parser.hpp>
std::string const sample = R"(<root>
<chocos>
<choco>
<color>1</color>
<companies>
<comapany>10</company>
<comapany>11</company>
</companies>
</choco>
<choco>
<color>2</color>
<companies>
<comapany>12</company>
<comapany>13</company>
</companies>
</choco>
</chocos>
</root>)";
using boost::property_tree::ptree;
using companies_t = std::vector<int>;
struct choco_info {
int color;
companies_t companies;
};
using chocos_t = std::vector<choco_info>;
struct root_info {
//some members
chocos_t chocos;
};
bool read_companies(ptree const& pt, companies_t& into) {
for (auto& [name,child] : pt)
if (name == "comapany") // FIXME
into.push_back(child.get_value<int>());
else
return false;
return true;
}
bool read_chocos(ptree const& pt, chocos_t& into) {
for (auto& [name,child] : pt) {
if (name == "choco") {
choco_info ci;
ci.color = child.get("color", -1);
if (!read_companies(child.get_child("companies"), ci.companies))
return false;
into.push_back(std::move(ci));
} else {
return false;
}
}
return true;
}
#include <iostream>
int main() {
ptree pt;
{
std::istringstream iss(sample);
read_xml(iss, pt);
}
root_info result;
bool ok = true;
try {
ok = read_chocos(pt.get_child("root.chocos"), result.chocos);
} catch(std::exception const& e) {
std::cout << "Exception: " << e.what() << "\n";
ok = false;
}
if (ok) {
std::cout << "Parsed:\n";
for (auto& choco : result.chocos) {
std::cout << " - color " << choco.color << "\n";
for (int company : choco.companies)
std::cout << " * company " << company << "\n";
}
} else {
std::cout << "Error in input\n";
}
}
Prints
Parsed:
- color 1
* company 10
* company 11
- color 2
* company 12
* company 13

Using Boost.Spirit.Lex and stream iterators

I want use Boost.Spirit.Lex to lex a binary file; for this purpose I wrote the following program (here is an extract):
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/support_multi_pass.hpp>
#include <boost/bind.hpp>
#include <boost/ref.hpp>
#include <fstream>
#include <iterator>
#include <string>
namespace spirit = boost::spirit;
namespace lex = spirit::lex;
#define X 1
#define Y 2
#define Z 3
template<typename L>
class word_count_tokens : public lex::lexer<L>
{
public:
word_count_tokens () {
this->self.add
("[^ \t\n]+", X)
("\n", Y)
(".", Z);
}
};
class counter
{
public:
typedef bool result_type;
template<typename T>
bool operator () (const T &t, size_t &c, size_t &w, size_t &l) const {
switch (t.id ()) {
case X:
++w; c += t.value ().size ();
break;
case Y:
++l; ++c;
break;
case Z:
++c;
break;
}
return true;
}
};
int main (int argc, char **argv)
{
std::ifstream ifs (argv[1], std::ios::in | std::ios::binary);
auto first = spirit::make_default_multi_pass (std::istream_iterator<char> (ifs));
auto last = spirit::make_default_multi_pass (std::istream_iterator<char> ());
size_t w, c, l;
word_count_tokens<lex::lexertl::lexer<>> word_count_functor;
w = c = l = 0;
bool r = lex::tokenize (first, last, word_count_functor, boost::bind (counter (), _1, boost::ref (c), boost::ref (w), boost::ref (l)));
ifs.close ();
if (r) {
std::cout << l << ", " << w << ", " << c << std::endl;
}
return 0;
}
The build returns the following error:
lexer.hpp:390:46: error: non-const lvalue reference to type 'const char *' cannot bind to a value of unrelated type
Now, the error is due to definition of concrete lexer, lex::lexer<>; in fact its first parameter is defaulted to const char *. I obtain the same error also if I use spirit::istream_iterator or spirit::make_default_multi_pass (.....).
But if I specify the correct template parameters of lex::lexer<> I obtain a plethora of errors!
Solutions?
Update
I have putted all source file; it's the word_counter site's example.
Okay, since the question was changed, here's a new answer, addressing some points with the complete code sample.
Firstly, you need to use a custom token type. I.e.
word_count_tokens<lex::lexertl::lexer<lex::lexertl::token<boost::spirit::istream_iterator>>> word_count_functor;
// instead of:
// word_count_tokens<lex::lexertl::lexer<>> word_count_functor;
Obviously, it's customary to typedef lex::lexertl::token<boost::spirit::istream_iterator>
You need to use min_token_id instead of token IDs 1,2,3. Also, make it an enum for ease of maintenance:
enum token_ids {
X = lex::min_token_id + 1,
Y,
Z,
};
You can no longer just use .size() on the default token value() since the iterator range is not RandomAccessRange anymore. Instead, employ boost::distance() which is specialized for iterator_range:
++w; c += boost::distance(t.value()); // t.value ().size ();
Combining these fixes: Live On Coliru
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/support_istream_iterator.hpp>
#include <boost/bind.hpp>
#include <fstream>
namespace spirit = boost::spirit;
namespace lex = spirit::lex;
enum token_ids {
X = lex::min_token_id + 1,
Y,
Z,
};
template<typename L>
class word_count_tokens : public lex::lexer<L>
{
public:
word_count_tokens () {
this->self.add
("[^ \t\n]+", X)
("\n" , Y)
("." , Z);
}
};
struct counter
{
typedef bool result_type;
template<typename T>
bool operator () (const T &t, size_t &c, size_t &w, size_t &l) const {
switch (t.id ()) {
case X:
++w; c += boost::distance(t.value()); // t.value ().size ();
break;
case Y:
++l; ++c;
break;
case Z:
++c;
break;
}
return true;
}
};
int main (int argc, char **argv)
{
std::ifstream ifs (argv[1], std::ios::in | std::ios::binary);
ifs >> std::noskipws;
boost::spirit::istream_iterator first(ifs), last;
word_count_tokens<lex::lexertl::lexer<lex::lexertl::token<boost::spirit::istream_iterator>>> word_count_functor;
size_t w = 0, c = 0, l = 0;
bool r = lex::tokenize (first, last, word_count_functor,
boost::bind (counter (), _1, boost::ref (c), boost::ref (w), boost::ref (l)));
ifs.close ();
if (r) {
std::cout << l << ", " << w << ", " << c << std::endl;
}
}
When run on itself, prints
65, 183, 1665
I think the real problem is not shown. You don't show first or last and I have a feeling you might have temporaries there.
Here's a sample I came up with to verify, perhaps you can see what it is you're doing ---wrong--- differently :)
Live on Coliru (memory mapped an byte-vector, via const char*)
And this alternative (using spirit::istream_iterator)
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/qi.hpp>
#include <fstream>
#ifdef MEMORY_MAPPED
# include <boost/iostreams/device/mapped_file.hpp>
#endif
namespace /*anon*/
{
namespace qi =boost::spirit::qi;
namespace lex=boost::spirit::lex;
template <typename Lexer>
struct mylexer_t : lex::lexer<Lexer>
{
mylexer_t()
{
fileheader = "hello";
this->self = fileheader
| space [ lex::_pass = lex::pass_flags::pass_ignore ];
}
lex::token_def<lex::omit>
fileheader, space;
};
template <typename Iterator> struct my_grammar_t
: public qi::grammar<Iterator>
{
template <typename TokenDef>
my_grammar_t(TokenDef const& tok)
: my_grammar_t::base_type(header)
{
header = tok.fileheader;
BOOST_SPIRIT_DEBUG_NODE(header);
}
private:
qi::rule<Iterator> header;
};
}
namespace /* */ {
std::string safechar(char ch) {
switch (ch) {
case '\t': return "\\t"; break;
case '\0': return "\\0"; break;
case '\r': return "\\r"; break;
case '\n': return "\\n"; break;
}
return std::string(1, ch);
}
template <typename It>
std::string showtoken(const boost::iterator_range<It>& range)
{
std::ostringstream oss;
oss << '[';
std::transform(range.begin(), range.end(), std::ostream_iterator<std::string>(oss), safechar);
oss << ']';
return oss.str();
}
}
bool parsefile(const std::string& spec)
{
#ifdef MEMORY_MAPPED
typedef char const* It;
boost::iostreams::mapped_file mmap(spec.c_str(), boost::iostreams::mapped_file::readonly);
char const *first = mmap.const_data();
char const *last = first + mmap.size();
#else
typedef char const* It;
std::ifstream in(spec.c_str());
in.unsetf(std::ios::skipws);
std::string v(std::istreambuf_iterator<char>(in.rdbuf()), std::istreambuf_iterator<char>());
It first = &v[0];
It last = first+v.size();
#endif
typedef lex::lexertl::token<It /*, boost::mpl::vector<char, unsigned int, std::string> */> token_type;
typedef lex::lexertl::actor_lexer<token_type> lexer_type;
typedef mylexer_t<lexer_type>::iterator_type iterator_type;
try
{
static mylexer_t<lexer_type> mylexer;
static my_grammar_t<iterator_type> parser(mylexer);
auto iter = mylexer.begin(first, last);
auto end = mylexer.end();
bool r = qi::parse(iter, end, parser);
r = r && (iter == end);
if (!r)
std::cerr << spec << ": parsing failed at: \"" << std::string(first, last) << "\"\n";
return r;
}
catch (const qi::expectation_failure<iterator_type>& e)
{
std::cerr << "FIXME: expected " << e.what_ << ", got '";
for (auto it=e.first; it!=e.last; it++)
std::cerr << showtoken(it->value());
std::cerr << "'" << std::endl;
return false;
}
}
int main()
{
if (parsefile("input.bin"))
return 0;
return 1;
}
For the variant:
typedef boost::spirit::istream_iterator It;
std::ifstream in(spec.c_str());
in.unsetf(std::ios::skipws);
It first(in), last;

How to index and query STL map containers by multiple keys?

I came across one requirement where the record is stored as
Name : Employee_Id : Address
where Name and Employee_Id are supposed to be keys that is, a search function is to be provided on both Name and Employee Id.
I can think of using a map to store this structure
std::map< std:pair<std::string,std::string> , std::string >
// < < Name , Employee-Id> , Address >
but I'm not exactly sure how the search function will look like.
Boost.Multiindex
This is a Boost example
In the above example an ordered index is used but you can use also a hashed index:
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <string>
#include <iostream>
struct employee
{
int id_;
std::string name_;
std::string address_;
employee(int id,std::string name,std::string address):id_(id),name_(name),address_(address) {}
};
struct id{};
struct name{};
struct address{};
struct id_hash{};
struct name_hash{};
typedef boost::multi_index_container<
employee,
boost::multi_index::indexed_by<
boost::multi_index::ordered_unique<boost::multi_index::tag<id>, BOOST_MULTI_INDEX_MEMBER(employee,int,id_)>,
boost::multi_index::ordered_unique<boost::multi_index::tag<name>,BOOST_MULTI_INDEX_MEMBER(employee,std::string,name_)>,
boost::multi_index::ordered_unique<boost::multi_index::tag<address>, BOOST_MULTI_INDEX_MEMBER(employee,std::string,address_)>,
boost::multi_index::hashed_unique<boost::multi_index::tag<id_hash>, BOOST_MULTI_INDEX_MEMBER(employee,int,id_)>,
boost::multi_index::hashed_unique<boost::multi_index::tag<name_hash>, BOOST_MULTI_INDEX_MEMBER(employee,std::string,name_)>
>
> employee_set;
typedef boost::multi_index::index<employee_set,id>::type employee_set_ordered_by_id_index_t;
typedef boost::multi_index::index<employee_set,name>::type employee_set_ordered_by_name_index_t;
typedef boost::multi_index::index<employee_set,name_hash>::type employee_set_hashed_by_name_index_t;
typedef boost::multi_index::index<employee_set,id>::type::const_iterator employee_set_ordered_by_id_iterator_t;
typedef boost::multi_index::index<employee_set,name>::type::const_iterator employee_set_ordered_by_name_iterator_t;
typedef boost::multi_index::index<employee_set,id_hash>::type::const_iterator employee_set_hashed_by_id_iterator_t;
typedef boost::multi_index::index<employee_set,name_hash>::type::const_iterator employee_set_hashed_by_name_iterator_t;
int main()
{
employee_set employee_set_;
employee_set_.insert(employee(1, "Employer1", "Address1"));
employee_set_.insert(employee(2, "Employer2", "Address2"));
employee_set_.insert(employee(3, "Employer3", "Address3"));
employee_set_.insert(employee(4, "Employer4", "Address4"));
// search by id using an ordered index
{
const employee_set_ordered_by_id_index_t& index_id = boost::multi_index::get<id>(employee_set_);
employee_set_ordered_by_id_iterator_t id_itr = index_id.find(2);
if (id_itr != index_id.end() ) {
const employee& tmp = *id_itr;
std::cout << tmp.id_ << ", " << tmp.name_ << ", " << tmp .address_ << std::endl;
} else {
std::cout << "No records have been found\n";
}
}
// search by non existing id using an ordered index
{
const employee_set_ordered_by_id_index_t& index_id = boost::multi_index::get<id>(employee_set_);
employee_set_ordered_by_id_iterator_t id_itr = index_id.find(2234);
if (id_itr != index_id.end() ) {
const employee& tmp = *id_itr;
std::cout << tmp.id_ << ", " << tmp.name_ << ", " << tmp .address_ << std::endl;
} else {
std::cout << "No records have been found\n";
}
}
// search by name using an ordered index
{
const employee_set_ordered_by_name_index_t& index_name = boost::multi_index::get<name>(employee_set_);
employee_set_ordered_by_name_iterator_t name_itr = index_name.find("Employer3");
if (name_itr != index_name.end() ) {
const employee& tmp = *name_itr;
std::cout << tmp.id_ << ", " << tmp.name_ << ", " << tmp .address_ << std::endl;
} else {
std::cout << "No records have been found\n";
}
}
// search by name using an hashed index
{
employee_set_hashed_by_name_index_t& index_name = boost::multi_index::get<name_hash>(employee_set_);
employee_set_hashed_by_name_iterator_t name_itr = index_name.find("Employer4");
if (name_itr != index_name.end() ) {
const employee& tmp = *name_itr;
std::cout << tmp.id_ << ", " << tmp.name_ << ", " << tmp .address_ << std::endl;
} else {
std::cout << "No records have been found\n";
}
}
// search by name using an hashed index but the name does not exists in the container
{
employee_set_hashed_by_name_index_t& index_name = boost::multi_index::get<name_hash>(employee_set_);
employee_set_hashed_by_name_iterator_t name_itr = index_name.find("Employer46545");
if (name_itr != index_name.end() ) {
const employee& tmp = *name_itr;
std::cout << tmp.id_ << ", " << tmp.name_ << ", " << tmp .address_ << std::endl;
} else {
std::cout << "No records have been found\n";
}
}
return 0;
}
If you want to use std::map, you can have two separate containers, each one having adifferent key (name, emp id) and the value should be a pointer the structure, so that you will not have multiple copies of the same data.
Example with tew keys:
#include <memory>
#include <map>
#include <iostream>
template <class KEY1,class KEY2, class OTHER >
class MultiKeyMap {
public:
struct Entry
{
KEY1 key1;
KEY2 key2;
OTHER otherVal;
Entry( const KEY1 &_key1,
const KEY2 &_key2,
const OTHER &_otherVal):
key1(_key1),key2(_key2),otherVal(_otherVal) {};
Entry() {};
};
private:
struct ExtendedEntry;
typedef std::shared_ptr<ExtendedEntry> ExtendedEntrySptr;
struct ExtendedEntry {
Entry entry;
typename std::map<KEY1,ExtendedEntrySptr>::iterator it1;
typename std::map<KEY2,ExtendedEntrySptr>::iterator it2;
ExtendedEntry() {};
ExtendedEntry(const Entry &e):entry(e) {};
};
std::map<KEY1,ExtendedEntrySptr> byKey1;
std::map<KEY2,ExtendedEntrySptr> byKey2;
public:
void del(ExtendedEntrySptr p)
{
if (p)
{
byKey1.erase(p->it1);
byKey2.erase(p->it2);
}
}
void insert(const Entry &entry) {
auto p=ExtendedEntrySptr(new ExtendedEntry(entry));
p->it1=byKey1.insert(std::make_pair(entry.key1,p)).first;
p->it2=byKey2.insert(std::make_pair(entry.key2,p)).first;
}
std::pair<Entry,bool> getByKey1(const KEY1 &key1)
{
const auto &ret=byKey1[key1];
if (ret)
return std::make_pair(ret->entry,true);
return std::make_pair(Entry(),false);
}
std::pair<Entry,bool> getByKey2(const KEY2 &key2)
{
const auto &ret=byKey2[key2];
if (ret)
return std::make_pair(ret->entry,true);
return std::make_pair(Entry(),false);
}
void deleteByKey1(const KEY1 &key1)
{
del(byKey1[key1]);
}
void deleteByKey2(const KEY2 &key2)
{
del(byKey2[key2]);
}
};
int main(int argc, const char *argv[])
{
typedef MultiKeyMap<int,std::string,int> M;
M map1;
map1.insert(M::Entry(1,"aaa",7));
map1.insert(M::Entry(2,"bbb",8));
map1.insert(M::Entry(3,"ccc",9));
map1.insert(M::Entry(7,"eee",9));
map1.insert(M::Entry(4,"ddd",9));
map1.deleteByKey1(7);
auto a=map1.getByKey1(2);
auto b=map1.getByKey2("ddd");
auto c=map1.getByKey1(7);
std::cout << "by key1=2 (should be bbb ): "<< (a.second ? a.first.key2:"Null") << std::endl;
std::cout << "by key2=ddd (should be ddd ): "<< (b.second ? b.first.key2:"Null") << std::endl;
std::cout << "by key1=7 (does not exist): "<< (c.second ? c.first.key2:"Null") << std::endl;
return 0;
}
Output:
by key1=2 (should be bbb ): bbb
by key2=ddd (should be ddd ): ddd
by key1=7 (does not exist): Null
If EmployeeID is the unique identifier, why use other keys? I would use EmployeeID as the internal key everywhere, and have other mappings from external/human readable IDs (such as Name) to it.
C++14 std::set::find non-key searches solution
This method saves you from storing the keys twice, once one the indexed object and secondly on as the key of a map as done at: https://stackoverflow.com/a/44526820/895245
This provides minimal examples of the central technique that should be easier to understand first: How to make a C++ map container where the key is part of the value?
#include <cassert>
#include <set>
#include <vector>
struct Point {
int x;
int y;
int z;
};
class PointIndexXY {
public:
void insert(Point *point) {
sx.insert(point);
sy.insert(point);
}
void erase(Point *point) {
sx.insert(point);
sy.insert(point);
}
Point* findX(int x) {
return *(this->sx.find(x));
}
Point* findY(int y) {
return *(this->sy.find(y));
}
private:
struct PointCmpX {
typedef std::true_type is_transparent;
bool operator()(const Point* lhs, int rhs) const { return lhs->x < rhs; }
bool operator()(int lhs, const Point* rhs) const { return lhs < rhs->x; }
bool operator()(const Point* lhs, const Point* rhs) const { return lhs->x < rhs->x; }
};
struct PointCmpY {
typedef std::true_type is_transparent;
bool operator()(const Point* lhs, int rhs) const { return lhs->y < rhs; }
bool operator()(int lhs, const Point* rhs) const { return lhs < rhs->y; }
bool operator()(const Point* lhs, const Point* rhs) const { return lhs->y < rhs->y; }
};
std::set<Point*, PointCmpX> sx;
std::set<Point*, PointCmpY> sy;
};
int main() {
std::vector<Point> points{
{1, -1, 1},
{2, -2, 4},
{0, 0, 0},
{3, -3, 9},
};
PointIndexXY idx;
for (auto& point : points) {
idx.insert(&point);
}
Point *p;
p = idx.findX(0);
assert(p->y == 0 && p->z == 0);
p = idx.findX(1);
assert(p->y == -1 && p->z == 1);
p = idx.findY(-2);
assert(p->x == 2 && p->z == 4);
}