I quickly wrote the below class for this question.
I'm looking for a way to merge addFruit() with removeFruit() to reduce the code.
They both use identical conditions but just different function call at the end.
My Code :
#include <iostream>
#include <string>
#include <vector>
class MyClass
{
public:
void addFruit(const std::string &str, int count)
{
if (str == "apples")
addToVec(apples, count);
else if (str == "oranges")
addToVec(oranges, count);
else if (str == "lemons")
addToVec(lemons, count);
else if (str == "melons")
addToVec(melons, count);
else if (str == "bananas")
addToVec(bananas, count);
else
std::cout << "Unknown Fruit : " << str << '\n';
}
void removeFruit(const std::string &str)
{
if (str == "apples")
removeFromVec(apples);
else if (str == "oranges")
removeFromVec(oranges);
else if (str == "lemons")
removeFromVec(lemons);
else if (str == "melons")
removeFromVec(melons);
else if (str == "bananas")
removeFromVec(bananas);
else
std::cout << "Unknown Fruit : " << str << '\n';
}
private:
void addToVec(std::vector<int> &vec, int count)
{
vec.push_back(count);
}
void removeFromVec(std::vector<int> &vec)
{
vec.pop_back();
}
std::vector<int> apples;
std::vector<int> oranges;
std::vector<int> lemons;
std::vector<int> melons;
std::vector<int> bananas;
};
Any clever way to nicely merge the two functions so I can reduce the code?
Make an additional function e.g. determineTargetVector(const std::string &str) which returns the corresponding vector, where you want to insert/remove an element, so you have no redundant conditions. Also its nice to have only a single reponsibility for each function.
For example:
std::vector<int> *determineTargetVector(const std::string &str)
{
if (str == "apples")
return &apples;
else if (str == "oranges")
return &oranges;
else if (str == "lemons")
.
.
.
else
//something invalid, to check for in superior function
return nullptr;
}
The easiest solution might be to use a std::map for those vectors:
std::map<std::string,std::vector<int>> fruitVecs;
The key values of the map would be "apples", "oranges", "bananas" etc.
Thus you can easily access the corresponding vector for any operation through the map.
A code that selects the vector to be used and then perform the action can be used :
class MyClass
{
public:
void addFruit(const std::string &str, int count)
{
auto vec = selectVector(str);
if(vec != nullptr)
addToVec(*vec, count);
else
std::cout << "Unknown Fruit : " << str << '\n';
}
void removeFruit(const std::string &str)
{
auto vec = selectVector(str);
if(vec != nullptr)
removeFromVec(*vec);
else
std::cout << "Unknown Fruit : " << str << '\n';
}
private:
std::vector<int> *selectVector(const std::string &str)
{
if (str == "apples")
return &apples;
else if (str == "oranges")
return &oranges;
else if (str == "lemons")
return &lemons;
else if (str == "melons")
return &melons;
else if (str == "bananas")
return &bananas;
else
return nullptr;
}
void addToVec(std::vector<int> &vec, int count)
{
vec.push_back(count);
}
void removeFromVec(std::vector<int> &vec)
{
vec.pop_back();
}
std::vector<int> apples;
std::vector<int> oranges;
std::vector<int> lemons;
std::vector<int> melons;
std::vector<int> bananas;
};
What about below solution. That will also allow you add/remove known fruits easily by adding/removing line in constructor.
#include <iostream>
#include <string>
#include <vector>
#include <map>
class MyClass
{
public:
MyClass()
{
allowedFruits["apples"] = {};
allowedFruits["oranges"] = {};
allowedFruits["lemons"] = {};
allowedFruits["melons"] = {};
allowedFruits["bananas"] = {};
}
void addFruit(const std::string &str, int count)
{
auto it = allowedFruits.find(str);
if(it != MyClass::allowedFruits.end()){
it->second.push_back(count);
}
else {
std::cout << "Unknown Fruit : " << str << '\n';
}
}
void removeFruit(const std::string &str)
{
auto it = allowedFruits.find(str);
if(it != allowedFruits.end()){
// my be some check here
it->second.pop_back();
}
else {
std::cout << "Unknown Fruit : " << str << '\n';
}
}
private:
std::map<std::string,std::vector<int>> allowedFruits;
};
Without changing the interface you could do it like this:
std::vector<int>& pickVector(std::string str) {
// put all the switch here and return a reference to the correct vector
}
void addFruit(const std::string &str, int count)
{
addToVec(pickVector(str),count);
}
I would go a little functionnal by passing the function to apply.
#include <iostream>
#include <string>
#include <vector>
#include <functional>
class MyClass
{
public:
void addFruit(const std::string &str, int count)
{
searchAndApplyHelper(str, std::bind(&MyClass::addToVec, *this, std::placeholders::_1, count));
}
void removeFruit(const std::string &str)
{
searchAndApplyHelper(str, std::bind(&MyClass::removeFromVec, *this, std::placeholders::_1));
}
private:
template <class Func>
void searchAndApplyHelper(const std::string str, Func f)
{
if (str == "apples")
f(apples);
else if (str == "oranges")
f(oranges);
else if (str == "lemons")
f(lemons);
else if (str == "melons")
f(melons);
else if (str == "bananas")
f(bananas);
else
std::cout << "Unknown Fruit : " << str << '\n';
}
void addToVec(std::vector<int> &vec, int count)
{
vec.push_back(count);
}
void removeFromVec(std::vector<int> &vec)
{
vec.pop_back();
}
std::vector<int> apples;
std::vector<int> oranges;
std::vector<int> lemons;
std::vector<int> melons;
std::vector<int> bananas;
};
I did it with a template but you could use std::function as well.
I would do this:
class MyClass
{
public:
void addFruit(const std::string &str, int count)
{
doToFruit(str, [&](std::vector<int> &vec){ addToVec(vec, count); });
}
void removeFruit(const std::string &str)
{
doToFruit(str, [&](std::vector<int> &vec){ removeFromVec(vec); });
}
private:
template<typename Callable>
void doToFruit(const std::string &str, const Callable &func)
{
std::pair<const char*, std::vector<int>&> fruits[] = {
{"apple", apples}, {"oranges", oranges}, {"lemons", lemons},
{"melons", melons}, {"bananas", bananas}
};
for (auto i : fruits)
if (str == i.first)
return func(i.second);
std::cout << "Unknown Fruit : " << str << '\n';
}
void addToVec(std::vector<int> &vec, int count)
{
vec.push_back(count);
}
void removeFromVec(std::vector<int> &vec)
{
vec.pop_back();
}
std::vector<int> apples;
std::vector<int> oranges;
std::vector<int> lemons;
std::vector<int> melons;
std::vector<int> bananas;
};
You can use pointer to members if you want better performance:
class MyClass
{
public:
void addFruit(const std::string &str, int count)
{
doToFruit(str, [&](std::vector<int> &vec){ addToVec(vec, count); });
}
void removeFruit(const std::string &str)
{
doToFruit(str, [&](std::vector<int> &vec){ removeFromVec(vec); });
}
private:
template<typename Callable>
void doToFruit(const std::string &str, const Callable &func)
{
auto iFound = fruits.find(str);
if (iFound != fruits.end())
return func(this->*(iFound->second));
std::cout << "Unknown Fruit : " << str << '\n';
}
void addToVec(std::vector<int> &vec, int count)
{
vec.push_back(count);
}
void removeFromVec(std::vector<int> &vec)
{
vec.pop_back();
}
std::vector<int> apples;
std::vector<int> oranges;
std::vector<int> lemons;
std::vector<int> melons;
std::vector<int> bananas;
static std::unordered_map<std::string, std::vector<int> MyClass::*> fruits;
};
std::unordered_map<std::string, std::vector<int> MyClass::*> MyClass::fruits = {
{"apple", &MyClass::apples}, {"oranges", &MyClass::oranges},
{"lemons", &MyClass::lemons}, {"melons", &MyClass::melons},
{"bananas", &MyClass::bananas}
};
Using C++17 you could use optional arguments:
void addRemoveFruit(const std::string &str, std::optional<int> count = std::nullopt)
{
if (str == "apples")
addRemoveVec(apples, count);
else if (str == "oranges")
addRemoveVec(oranges, count);
else if (str == "lemons")
addRemoveVec(lemons, count);
else if (str == "melons")
addRemoveVec(melons, count);
else if (str == "bananas")
addRemoveVec(bananas, count);
else
std::cout << "Unknown Fruit : " << str << '\n';
}
and:
void addRemoveVec(std::vector<int> &vec, std::optional<int> count = std::nullopt)
{
if(count.has_value()) {
vec.push_back(count.value());
} else {
vec.pop_back();
}
}
This way existing calls to addFruit/removeFruit only need to be changed to addRemoveFruit without having to change the parameters passed.
To stick with your pattern of multiple distinct vectors, I'd recommend using an internal enum that decides the method, while the choice of the vector can be done in one place:
class MyClass
{
public:
void addFruit(const std::string &str, int count)
{
addOrRemoveFruit(str, count, Method::ADD);
}
void removeFruit(const std::string &str)
{
addOrRemoveFruit(str, 0, Method::REMOVE);
}
private:
enum class Method
{
ADD,
REMOVE
};
void addOrRemoveFruit(const std::string &str, int count, Method method)
{
if (str == "apples")
addOrRemoveFruitImpl(apples, count, method);
else if (str == "oranges")
addOrRemoveFruitImpl(oranges, count, method);
else if (str == "lemons")
addOrRemoveFruitImpl(lemons, count, method);
else if (str == "melons")
addOrRemoveFruitImpl(melons, count, method);
else if (str == "bananas")
addOrRemoveFruitImpl(bananas, count, method);
else
std::cout << "Unknown Fruit : " << str << '\n';
}
void addOrRemoveFruitImpl(std::vector<int> &vec, int count, Method method)
{
if (method == Method::ADD)
vec.push_back(count);
else
vec.pop_back();
}
std::vector<int> apples;
std::vector<int> oranges;
std::vector<int> lemons;
std::vector<int> melons;
std::vector<int> bananas;
};
However even better would be to use a map:
class MyClass
{
public:
void addFruit(const std::string &str, int count)
{
fruits[str].push_back(count);
}
void removeFruit(const std::string &str)
{
if (fruits.count(str) > 0)
fruits[str].pop_back();
}
private:
std::unordered_map<std::string, std::vector<int>> fruits;
};
Related
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
I have a custom vector of my user defined type vector
First vector gets filled with elements through stdin, then i sort it and try to find duplicates in it and save them
i've managed to find all unique elements, but i need to find and get a vector of duplicates
I need a hint or a simple solution for this problem
here's my code below:
Agressor.h
#ifndef Agressor_h
#define Agressor_h
#include <string>
#include <vector>
using namespace std;
class Agressor{
public:
/*const char**/ string traderIdentifier;
/*const char**/ string side;
int quantity;
int price;
vector<Agressor> v;
void display(){
cout << traderIdentifier << " " << side << " " << quantity << " " << price << endl;
}
explicit Agressor(){
}
~Agressor(){
}
friend ostream &operator<<(ostream& stream, const Agressor& item);
const friend bool operator > (const Agressor &a1, const Agressor &a2);
// const friend bool operator == (const Agressor &a1, const Agressor &a2);
/* vector<Agressor>& operator[](int i ){
return v[i];
}*/
};
ostream &operator<<(ostream& stream, const Agressor& item) {
string side = "";
if(item.side == "B"){
side = '+';
}else{
if(item.side == "S"){
side = "-";
}
}
stream << item.traderIdentifier << side << item.quantity << "#" << item.price << "\n";
return stream;
}
const bool operator == (const Agressor &a1, const Agressor &a2){
bool isEqual = false;
if((a1.price*a1.quantity == a2.price*a2.quantity) && (a1.traderIdentifier == a2.traderIdentifier) && (a1.side == a2.side)){
isEqual = true;
}
return(isEqual);
}
const bool operator > (const Agressor &a1, const Agressor &a2){
bool isGreater = false;
if(a1.price*a1.quantity > a2.price*a2.quantity){
isGreater = true;
}
return(isGreater);
}
#endif /* Agressor_h */
main.cpp
#include <iostream>
#include "Agressor.h"
#include <sstream>
using namespace std;
vector<string> &split(const string &s, char delim, vector<string> &elems)
{
stringstream ss(s);
string item;
while (getline(ss, item, delim))
{
elems.push_back(item);
}
return elems;
}
vector<string> split(const string &s, char delim)
{
vector<string> elems;
split(s, delim, elems);
return elems;
}
bool equal_comp(const Agressor& a1, const Agressor& a2){
if((a1.price*a1.quantity == a2.price*a2.quantity) && (a1.traderIdentifier == a2.traderIdentifier) && (a1.side == a2.side)){
return true;
}
return false;
}
int main(int argc, const char * argv[]) {
Agressor agr;
while (true) {
std::string sText;
cout << "enter query:" << endl;
std::getline(std::cin, sText);
if(sText == "q"){
cout << "Program terminated by user" << endl;
break;
}else{
std::vector<std::string> sWords = split(sText, ' ');
agr.traderIdentifier = sWords[0];
agr.side = sWords[1];
agr.quantity = stoi(sWords[2]);
agr.price = stoi(sWords[3]);
agr.v.push_back(agr);
vector<Agressor>::iterator it;
sort(agr.v.begin(), agr.v.end(), greater<Agressor>());
//unique(agr.v.begin(), agr.v.end(), equal_comp);
for (vector<Agressor>::const_iterator i = agr.v.begin(); i != agr.v.end(); ++i)
cout << *i << ' ';
}
}
cout << "here we go..." << endl;
vector<Agressor>::iterator it;
sort(agr.v.begin(), agr.v.end(), greater<Agressor>());
//it = unique(agr.v.begin(),agr.v.end(), equal_comp);
//agr.v.resize( distance(agr.v.begin(),it) );
agr.v.erase(unique(agr.v.begin(),agr.v.end(), equal_comp), agr.v.end());
copy(agr.v.begin(), agr.v.end(), ostream_iterator<Agressor>(cout, "\n"));
return 0;
}
You might use something like:
template <typename T>
std::vector<T> get_duplicates(const std::vector<T>& v)
{
// expect sorted vector
auto it = v.begin();
auto end = v.end();
std::vector<T> res;
while (it != end) {
it = std::adjacent_find(it, end);
if (it != end) {
++it;
res.push_back(*it);
}
}
return res;
}
std::unique overwrites duplicate values with later non-duplicate values. You can implement a similar algorithm that moves the values to somewhere.
template<class ForwardIt, class OutputIt, class BinaryPredicate>
ForwardIt unique_retain(ForwardIt first, ForwardIt last, OutputIt d_first, BinaryPredicate p)
{
if (first == last)
return last;
ForwardIt result = first;
while (++first != last) {
if (!p(*result, *first) && ++result != first) {
*d_first++ = std::move(*result);
*result = std::move(*first);
}
}
return ++result;
}
(adapted from this possible implementation of std::unique)
You would then use it like
vector<Agressor> dups;
sort(agr.v.begin(), agr.v.end(), greater<Agressor>());
auto it = unique_retain(agr.v.begin(),agr.v.end(), std::back_inserter(dups), equal_comp);
agr.v.erase(it, agr.v.end());
I am using boost library to manipulate a JSON file and I would like to access to a specific index of an array in this JSON.
boost::property_tree::ptree& jsonfile;
const boost::property_tree::ptree& array =
jsonfile.get_child("my_array");
What I would like to do is accessing to the value stored at index :
// This code does not compile
int value = array[index].get < int > ("property");
Just code it using the iterators:
template <typename T = std::string>
T element_at(ptree const& pt, std::string name, size_t n) {
return std::next(pt.get_child(name).find(""), n)->second.get_value<T>();
}
If you want to have the index checked for bounds:
template <typename T = std::string>
T element_at_checked(ptree const& pt, std::string name, size_t n) {
auto r = pt.get_child(name).equal_range("");
for (; r.first != r.second && n; --n) ++r.first;
if (n || r.first==r.second)
throw std::range_error("index out of bounds");
return r.first->second.get_value<T>();
}
Demo
Live On Coliru
#include <boost/property_tree/json_parser.hpp>
#include <iostream>
using boost::property_tree::ptree;
template <typename T = std::string>
T element_at(ptree const& pt, std::string name, size_t n) {
return std::next(pt.get_child(name).find(""), n)->second.get_value<T>();
}
template <typename T = std::string>
T element_at_checked(ptree const& pt, std::string name, size_t n) {
auto r = pt.get_child(name).equal_range("");
for (; r.first != r.second && n; --n) ++r.first;
if (n || r.first==r.second)
throw std::range_error("index out of bounds");
return r.first->second.get_value<T>();
}
int main() {
ptree pt;
{
std::istringstream iss("{\"a\":[1, 2, 3, 4, 5, 6]}");
read_json(iss, pt);
}
write_json(std::cout, pt, false);
// get the 4th element:
std::cout << element_at_checked(pt, "a", 3) << "\n";
// get it as int
std::cout << element_at_checked<int>(pt, "a", 3) << "\n";
// get non-existent array:
try { std::cout << element_at_checked<int>(pt, "b", 0) << "\n"; } catch(std::exception const& e) { std::cout << e.what() << "\n"; }
try { std::cout << element_at_checked<int>(pt, "a", 6) << "\n"; } catch(std::exception const& e) { std::cout << e.what() << "\n"; }
}
Prints
{"a":["1","2","3","4","5","6"]}
4
4
No such node (b)
index out of bounds
I have two loops to iterate a collection:
the first one adds missing elements into the collection.
the second one updates existing instances into the collection.
How can I replace the loops by standard library functions?
// add missing elements into the collection (if any)
for (auto i = collection.size(); i < objectTypes.size() + startIdx; i++)
{
collection.push_back(CNode(i));
}
// update elements of the collection
for (const auto& objectType : objectTypes)
{
collection[startIdx++].SetObjectType(objectType);
}
This question is a further step to this one.
Here a complete sample that compiles:
#include <string>
#include <iostream>
#include <vector>
#include <regex>
class CObject
{
std::string _objectType;
public:
CObject() : _objectType("n/a") {}
void SetObjectType(std::string objectType) { _objectType = objectType; }
std::string GetObjectType() const { return _objectType; }
};
class CNode
{
int _id;
CObject _object;
public:
explicit CNode(int id) : _id(id) {}
void SetObjectType(std::string objectType) { _object.SetObjectType(objectType); }
std::string GetObjectType() const { return _object.GetObjectType(); }
};
std::vector<std::string> SplitLine(std::string const& line, std::string seps)
{
std::regex regxSeps(seps); // the dot character needs to be escaped in a regex
std::sregex_token_iterator rit(line.begin(), line.end(), regxSeps, -1);
return std::vector<std::string>(rit, std::sregex_token_iterator());
}
static int ParseLine(std::string line, std::string seps, size_t startIdx, std::vector<CNode>& collection)
{
if (startIdx > collection.size())
{
throw std::invalid_argument("the start index is out of range");
}
auto objectTypes = SplitLine(line, seps);
for (auto missingIdx = collection.size(); missingIdx < objectTypes.size() + startIdx; missingIdx++)
{
collection.push_back(CNode(missingIdx));
}
for (const auto& objectType : objectTypes)
{
collection[startIdx++].SetObjectType(objectType);
}
return (startIdx - 1);
}
int main()
{
std::string seps = "\\."; // the dot character needs to be escaped in a regex
// 2 3 4 5 6 7 8 9
std::string line = "abc.def.ghi.klm.nop.qrs.tuv.wxyz";
std::vector<CNode> collection{ CNode(0), CNode(1), CNode(2) , CNode(3) , CNode(4) , CNode(5) };
auto startAt = 2;
try
{
auto collection_size = ParseLine(line, seps, startAt, collection);
std::cout << collection_size << std::endl;
for (auto value : collection)
{
std::cout << value.GetObjectType() << std::endl;
}
}
catch (std::invalid_argument& e)
{
std::cout << " out of range exception " << e.what() << std::endl;
}
}
Perhaps something similar to this would work (I have not tested it):
auto i = collection.size();
std::transform (objectTypes.begin(), objectTypes.end(),
std::back_inserter(collection),
[&](const ObjectType& ot) {
CNode ct(++i);
ct.SetObjectType(ot);
return ct;
});
Is there any reason also not to add an objectType parameter to the CNode constructor?
In case someone is interested to get the complete code of the function, below the final solution using the standard library to replace the two loops to insert and update the collection:
// Compute the number of elements to insert and to update
auto numInserts = startIdx + objectTypes.size() - collection.size();
auto numUpdates = collection.size() - startIdx;
// update the elements that already exists in the collection
std::for_each( objectTypes.begin(),
objectTypes.begin() + numUpdates,
[&](const std::string objectType)
{ collection[startIdx++].SetObjectType(objectType); });
// add the missing elements into the collection (if any)
std::transform( objectTypes.end() - numInserts,
objectTypes.end(),
std::back_inserter(collection),
[&](const std::string& objectType)
{ return CNode(++startIdx, objectType); });
Here a complete sample that compiles:
#include <string>
#include <iostream>
#include <vector>
#include <regex>
class CObject
{
std::string _objectType;
public:
CObject() : _objectType("n/a") {}
explicit CObject(std::string objectType) : _objectType(objectType) {};
void SetObjectType(std::string objectType) { _objectType = objectType; }
std::string GetObjectType() const { return _objectType; }
};
class CNode
{
int _id;
CObject _object;
public:
explicit CNode(int id) : _id(id) {}
explicit CNode(int id, std::string objectType) : _id(id), _object(objectType) {}
void SetObjectType(std::string objectType) { _object.SetObjectType(objectType); }
std::string GetObjectType() const { return _object.GetObjectType(); }
};
std::vector<std::string> SplitLine(std::string const& line, std::string seps)
{
std::regex regxSeps(seps); // the dot character needs to be escaped in a regex
std::sregex_token_iterator rit(line.begin(), line.end(), regxSeps, -1);
return std::vector<std::string>(rit, std::sregex_token_iterator());
}
static int ParseLineWithLoops(std::string line, std::string seps, size_t startIdx, std::vector<CNode>& collection)
{
if (startIdx > collection.size())
{
throw std::invalid_argument("the start index is out of range");
}
auto objectTypes = SplitLine(line, seps);
// expand the collection if needed
for (auto idx = collection.size(); idx < objectTypes.size() + startIdx; idx++)
{
collection.push_back(CNode(idx));
}
// update the types of elements into the collection
for (const auto& objectType : objectTypes)
{
collection[startIdx++].SetObjectType(objectType);
}
return (startIdx - 1);
}
static int ParseLineWithStdTransform(std::string line, std::string seps, size_t startIdx, std::vector<CNode>& collection)
{
if (startIdx > collection.size())
{
throw std::invalid_argument("the start index is out of range");
}
auto objectTypes = SplitLine(line, seps);
// Compute the number of elements to insert and to update
auto numInserts = startIdx + objectTypes.size() - collection.size();
auto numUpdates = collection.size() - startIdx;
// update the elements that already exists in the collection
std::for_each( objectTypes.begin(),
objectTypes.begin() + numUpdates,
[&](const std::string objectType) { collection[startIdx++].SetObjectType(objectType); });
// add the missing elements into the collection (if any)
std::transform( objectTypes.end() - numInserts,
objectTypes.end(),
std::back_inserter(collection),
[&](const std::string& objectType) { return CNode(++startIdx, objectType); });
return (collection.size() - 1);
}
int main()
{
std::string seps = "\\."; // the dot character needs to be escaped in a regex
// 2 3 4 5 6 7 8 9
std::string line = "abc.def.ghi.klm.nop.qrs.tuv.wxyz";
auto startAt = 2;
std::vector<CNode> collection1{ CNode(0), CNode(1), CNode(2) , CNode(3) , CNode(4) , CNode(5) };
try
{
auto collection_size = ParseLineWithStdTransform(line, seps, startAt, collection1);
std::cout << collection_size << std::endl;
for (auto value : collection1)
{
std::cout << value.GetObjectType() << std::endl;
}
}
catch (std::invalid_argument& e)
{
std::cout << " out of range exception " << e.what() << std::endl;
}
std::vector<CNode> collection2{ CNode(0), CNode(1), CNode(2) , CNode(3) , CNode(4) , CNode(5) };
try
{
auto collection_size = ParseLineWithLoops(line, seps, startAt, collection2);
std::cout << collection_size << std::endl;
for (auto value : collection2)
{
std::cout << value.GetObjectType() << std::endl;
}
}
catch (std::invalid_argument& e)
{
std::cout << " out of range exception " << e.what() << std::endl;
}
}
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);
}