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

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

Related

Find element in boost multi_index_container

In my code I need to have a functionality to iterate over all elements and check if there some element already exists possibly as soon as possible, so my choice fell on boost multi index container where I can use vector and unordered_set interface for my class Animal at the same time. The problem is that I am not able to find some element through unordered_set interface since I replaced key from std::string to std::array<char, 50> and adjusted the code, and I don't know what I am doing wrong ?
code:
https://wandbox.org/permlink/dnCaEzYVdXkTFBGo
#include <array>
#include <algorithm>
#include <iostream>
#include <chrono>
#include <string>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <memory>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/identity.hpp>
int constexpr elements_size{ 1'000'000 };
struct Animal
{
Animal(std::string name, std::string description, int leg, int age, double maxSpeed) noexcept :
description_{std::move(description)}, leg_{leg}, age_{age}, maxSpeed_{maxSpeed}
{
std::copy(name.begin(), name.end(), name_.data());
}
Animal(std::string const& name, std::string const& description) noexcept :
description_{description}
{
std::copy(name.begin(), name.end(), name_.data());
}
Animal(Animal&& animal) noexcept
{
name_ = name_;
description_ = std::move(animal).description_;
leg_ = animal.leg_;
age_ = animal.age_;
maxSpeed_ = animal.maxSpeed_;
}
Animal(Animal const& animal) noexcept
{
name_ = animal.name_;
description_ = animal.description_;
leg_ = animal.leg_;
age_ = animal.age_;
maxSpeed_ = animal.maxSpeed_;
}
Animal& operator=(Animal&& animal) noexcept
{
name_ = name_;
description_ = std::move(animal).description_;
leg_ = animal.leg_;
age_ = animal.age_;
maxSpeed_ = animal.maxSpeed_;
return *this;
}
Animal& operator=(Animal const& animal) noexcept
{
name_ = animal.name_;
description_ = animal.description_;
leg_ = animal.leg_;
age_ = animal.age_;
maxSpeed_ = animal.maxSpeed_;
return *this;
}
std::array<char, 50> name_;
std::string description_;
int leg_{0};
int age_{0};
double maxSpeed_{0.0};
};
struct Hasher
{
bool print_;
Hasher(bool print = false): print_{print} {}
std::size_t operator()(std::array<char, 50> const& name) const
{
if (print_)
std::cout << "array hash" << std::hash<std::string_view>{}(name.data()) << std::endl;
return std::hash<std::string_view>{}(name.data());
}
std::size_t operator()(std::string const& name) const
{
if (print_)
std::cout << "string hash" << std::hash<std::string_view>{}(name.c_str()) << std::endl;
return std::hash<std::string_view>{}(name.c_str());
}
std::size_t operator()(const char* name) const
{
if (print_)
std::cout << "char hash" << std::hash<std::string_view>{}(name) << std::endl;
return std::hash<std::string_view>{}(name);
}
};
struct KeysComparator
{
bool operator()(std::array<char, 50> const& a1, std::array<char, 50> const& a2) const {return a1 == a2; }
template <typename T>
bool operator()(std::string const& n1, T const& t) const
{
std::cout << "### value.name_" << t.value.name_.data() << ", n1: " << n1 << std::endl;
return n1 == t.value.name_.data();
}
};
template<typename TimePoint>
std::string getElapsedTime(TimePoint const& start, TimePoint const& end)
{
auto micro = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
auto milli = std::chrono::duration_cast<std::chrono::milliseconds>(micro);
auto sec = std::chrono::duration_cast<std::chrono::seconds>(milli);
return {std::to_string(micro.count()) + " µs, " + std::to_string(milli.count()) + " ms, " + std::to_string(sec.count()) + " s"};
}
template<typename TimePoint>
void printStatistics(TimePoint const& emplace_start, TimePoint const& emplace_end, TimePoint const& iterate_start, TimePoint const& iterate_end,
TimePoint const& find_start, TimePoint const& find_end, intmax_t const sum, std::string target)
{
std::cout << "Elapsed time emplace: " << getElapsedTime(emplace_start, emplace_end)
<< " | iterate: " << getElapsedTime(iterate_start, iterate_end)
<< " | find: " << getElapsedTime(find_start, find_end)
<< ", sum:" << sum << " , calculation for " << target << std::endl;
}
void test()
{
using namespace boost::multi_index;
using Animal_multi = multi_index_container<Animal, indexed_by<
random_access<>,
hashed_unique<
composite_key<Animal, member<Animal, std::array<char, 50>, &Animal::name_>>,
composite_key_hash<Hasher>,
composite_key_equal_to<KeysComparator>>
>>;
Animal_multi container;
auto emplace_start = std::chrono::steady_clock::now();
for (auto i = 0; i < elements_size; ++i)
container.emplace_back("the really long name of some animal 12345678910_" + std::to_string(i),
"bla bla bla bla bla bla bla bla bla bla bla bla bla", 4, i, i + 2);
auto emplace_end = std::chrono::steady_clock::now();
intmax_t sum{0};
auto iterate_start = std::chrono::steady_clock::now();
for (auto const& e : container)
sum += e.age_;
auto iterate_end = std::chrono::steady_clock::now();
KeysComparator key_comparator;
Hasher hasher{true};
auto find_start = std::chrono::steady_clock::now();
auto &container_interface = container.get<1>();
auto isSucceeded = container_interface.count("the really long name of some animal 12345678910_" + std::to_string(elements_size-1),
hasher, key_comparator);
if (not isSucceeded)
std::cout << "WARN: Element has not been found." << std::endl;
auto find_end = std::chrono::steady_clock::now();
printStatistics(emplace_start, emplace_end, iterate_start, iterate_end, find_start, find_end, sum, "Animal_multi (boost multi_index)");
}
int main()
{
test();
return 0;
}
There are a number of bugs like in the move constructor:
name_ = name_; // oops this does nothing at all
Just follow Rule Of Zero. This will also inform you that std::string copy/assignment are not noexcept.
The name copy should probably be length-limited:
std::copy_n(name.begin(), std::min(name.size(), name_.size()), name_.data());
At this point I notice something that might explain your trouble: you don't NUL-terminate, nor make sure that the array is 0-initialized.
BINGO
Indeed, just a few lines down I spot:
return std::hash<std::string_view>{}(name.data());
That's... UB! Your string_view might contain indeterminate data, but what's worse, you would NEVER have copied the terminating NUL character. So, std::string_view will model a string with indeterminate length which WILL likely exceed 50.
Read here about Nasal Demons (UB)
Such are the perils of skipping standard library types for the old C craft.
First Dig
So, here's the entirety of the class with equal/better characteristics:
using Name = std::array<char, 50>;
struct Animal {
Animal(std::string_view name, std::string description,
int leg = 0, int age = 0, double maxSpeed = 0) noexcept
: name_{0}, // zero initialize!
description_{std::move(description)},
leg_{leg},
age_{age},
maxSpeed_{maxSpeed}
{
constexpr auto Capacity = std::tuple_size<Name>::value;
constexpr auto MaxLen = Capacity - 1; // reserve NUL char
assert(name.length() < MaxLen);
std::copy_n(name.data(), std::min(name.length(), MaxLen), name_.data());
}
//Animal ( Animal&& animal ) noexcept = default;
//Animal ( Animal const& animal ) = default;
//Animal& operator= ( Animal&& animal ) noexcept = default;
//Animal& operator= ( Animal const& animal ) = default;
Name name_;
std::string description_;
int leg_{0};
int age_{0};
double maxSpeed_{0.0};
};
Improving: FixedString
This just screams for a better Name type. How about, FixedString:
template <size_t N> struct FixedString {
static_assert(N > 1); // require space for NUL char
FixedString(std::string_view s) : data_{0} {
if (s.length() >= N)
throw std::length_error("FixedString");
std::copy_n(s.data(), std::min(s.length(), N - 1), data());
}
std::string_view str() const { return { data(), size() }; }
operator std::string_view() const { return str(); }
auto data() const { return data_.data(); }
auto data() { return data_.data(); }
auto c_str() const { return data_.data(); }
auto c_str() { return data_.data(); }
auto begin() const { return data_.begin(); }
auto end() const { return data_.end(); }
auto begin() { return data_.begin(); }
auto end() { return data_.end(); }
size_t size() const {
auto terminator = std::memchr(data(), 0, data_.max_size());
return terminator
? static_cast<char const*>(terminator) - data()
: data_.max_size();
};
bool operator<(FixedString const& rhs) const { return str() < rhs.str(); }
bool operator==(FixedString const& rhs) const { return str() == rhs.str(); }
bool operator!=(FixedString const& rhs) const { return str() != rhs.str(); }
// optimizations:
bool operator<(std::string_view const& rhs) const { return str() < rhs.substr(0, N-1); }
bool operator==(std::string_view const& rhs) const { return str() == rhs.substr(0, N-1); }
bool operator!=(std::string_view const& rhs) const { return str() != rhs.substr(0, N-1); }
private:
std::array<char, N> data_;
};
Now you can simply
using Name = FixedString<50>;
And all your Names will magically (and safely) convert to and from string views.
using Name = FixedString<50>;
struct Animal {
Animal(std::string_view name, std::string description,
int leg = 0, int age = 0, double maxSpeed = 0) noexcept
: name_{name}, description_{std::move(description)},
leg_{leg}, age_{age}, maxSpeed_{maxSpeed}
{ }
Name name_;
std::string description_;
int leg_{0};
int age_{0};
double maxSpeed_{0.0};
};
Everything Simplifies With The Right Abstraction
This is the most important lesson I think I learned in my programming career: choosing the right abstraction leads to simplicity. Here, we evaporate two messy helpers:
using Hasher = std::hash<std::string_view>;
using KeysComparator = std::equal_to<Name>;
Boom. They do everything you had, but better.
Now, The Missing Element
After simplifying the whole thing to this it should become pretty obvious that a std::array<char, 50> can never correctly contain names longer than 50 characters. Indeed, checking the insertions:
auto emplace_start = Now();
size_t duplicates = 0;
for (auto i = 0; i < elements_size; ++i) {
auto [_, ok] = container.emplace_back(
make_name(i), "bla bla bla bla bla bla bla bla bla bla bla bla bla",
4, i, i + 2);
if (!ok) ++duplicates;
}
if (duplicates) {
std::cerr << "Oops, " << duplicates << " duplicate keys not inserted\n";
}
auto emplace_end = Now();
Reveals that:
Oops, 999990 duplicate keys not inserted
Elapsed time emplace: 116.491ms iterate: 0.000145ms find: 0.000597ms, sum:45 , calculation for Animal_multi (boost multi_index)
At least, now you replaced Undefined
Behaviour with
constraint checks.
Of course, just increasing the name capacity fixes it: [https://wandbox.org/permlink/6AamJfXe76nYALfR)
using Name = FixedString<60>;
Prints:
Elapsed time emplace: 594.475ms iterate: 18.6076ms find: 0.003138ms, sum:499999500000 , calculation for Animal_multi (boost multi_index)
Alternatively you can throw on Name construction with an overly long name: Live On Wandbox
FixedString(std::string_view s) : data_{0} {
if (s.length() >= N)
throw std::length_error("FixedString");
std::copy_n(s.data(), std::min(s.length(), N - 1), data());
}
Which duly prints
terminate called after throwing an instance of 'std::length_error'
what(): FixedString
Full Listing
This demo uses FixedString<60> to avoid the key errors:
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/multi_index/member.hpp>
#include <iostream>
#include <iomanip>
#include <chrono>
using namespace std::chrono_literals;
int constexpr elements_size{ 1'000'000 };
template <size_t N> struct FixedString {
static_assert(N > 1); // require space for NUL char
FixedString(std::string_view s) : data_{0} {
if (s.length() >= N)
throw std::length_error("FixedString");
std::copy_n(s.data(), std::min(s.length(), N - 1), data());
}
std::string_view str() const { return { data(), size() }; }
operator std::string_view() const { return str(); }
auto data() const { return data_.data(); }
auto data() { return data_.data(); }
auto c_str() const { return data_.data(); }
auto c_str() { return data_.data(); }
auto begin() const { return data_.begin(); }
auto end() const { return data_.end(); }
auto begin() { return data_.begin(); }
auto end() { return data_.end(); }
size_t size() const {
auto terminator = std::memchr(data(), 0, data_.max_size());
return terminator
? static_cast<char const*>(terminator) - data()
: data_.max_size();
};
bool operator<(std::string_view const& rhs) const { return str() < rhs.substr(0, N-1); }
bool operator==(std::string_view const& rhs) const { return str() == rhs.substr(0, N-1); }
bool operator!=(std::string_view const& rhs) const { return str() != rhs.substr(0, N-1); }
bool operator<(FixedString const& rhs) const { return str() < rhs.str(); }
bool operator==(FixedString const& rhs) const { return str() == rhs.str(); }
bool operator!=(FixedString const& rhs) const { return str() != rhs.str(); }
private:
std::array<char, N> data_;
};
using Name = FixedString<60>;
struct Animal {
Animal(std::string_view name, std::string description,
int leg = 0, int age = 0, double maxSpeed = 0) noexcept
: name_{name}, description_{std::move(description)},
leg_{leg}, age_{age}, maxSpeed_{maxSpeed}
{ }
Name name_;
std::string description_;
int leg_{0};
int age_{0};
double maxSpeed_{0.0};
};
using Hasher = std::hash<std::string_view>;
using KeysComparator = std::equal_to<Name>;
using Clock = std::chrono::steady_clock;
using Duration = Clock::duration;
static auto Now = Clock::now;
void printStatistics(Duration emplace, Duration iterate, Duration find,
intmax_t const sum, std::string target)
{
std::cout << "Elapsed time"
<< " emplace: " << (emplace/1.0ms) << "ms"
<< " iterate: " << (iterate/1.0ms) << "ms"
<< " find: " << (find/1.0ms) << "ms"
<< ", sum:" << sum
<< " , calculation for " << target
<< std::endl;
}
void test() {
namespace bmi = boost::multi_index;
using Animal_multi = bmi::multi_index_container<Animal,
bmi::indexed_by<
bmi::random_access<>,
bmi::hashed_unique<
bmi::tag<struct by_name>,
bmi::member<Animal, Name, &Animal::name_>, Hasher, KeysComparator>
>
>;
Animal_multi container;
auto make_name = [](size_t id) {
return "the really long name of some animal 12345678910_" + std::to_string(id);
};
auto emplace_start = Now();
size_t duplicates = 0;
for (auto i = 0; i < elements_size; ++i) {
auto [_, ok] = container.emplace_back(
make_name(i), "bla bla bla bla bla bla bla bla bla bla bla bla bla",
4, i, i + 2);
if (!ok) ++duplicates;
}
if (duplicates) {
std::cerr << "Oops, " << duplicates << " duplicate keys not inserted\n";
}
auto emplace_end = Now();
intmax_t sum{ 0 };
auto iterate_start = Now();
for (auto const& e : container) {
sum += e.age_;
}
auto iterate_end = Now();
auto find_start = Now();
{
auto& name_idx = container.get<by_name>();
auto last_key = make_name(elements_size - 1);
if (name_idx.count(std::string_view(last_key)) == 0u) {
std::cout << "WARN: Element has not been found." << std::endl;
}
}
auto find_end = Now();
printStatistics(
emplace_end - emplace_start,
iterate_end - iterate_start,
find_end - find_start, sum,
"Animal_multi (boost multi_index)");
}
int main() { test(); }

How to merge two functions with same conditions?

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;
};

Replace the loops to insert and update a collection by the Standard Library

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;
}
}

implement iterator on every elements of value containers against each key of map using boost iterator

How to implement an iterator of just on values of a map/unordered_map using boost::iterator_adaptor? I've tried following code but it does not work because of the line with comment.
Is there a solution to avoid the problem?
The question here is slightly different from map_values adapter example shown in boost code as here the value field in map is another container like list or vector and the requirement here is to iterate over all elements of those lists for every key of the map.
The deref of iterator is of type of value_type of those list/vector.The end of iterator is the end of list of last key
#include <vector>
#include <boost/unordered_map.hpp>
#include <cassert>
#include <iostream>
#include <boost/iterator/iterator_adaptor.hpp>
class DS {
public:
DS() : _map() {}
~DS() {
for (Map::iterator it = _map.begin(); it != _map.end(); ++it) {
delete (it->second);
}
}
void add(int key_, const std::vector< int > &value_)
{
IntList *ptr = new IntList(value_);
assert(ptr);
_map.insert(Map::value_type(key_, ptr));
}
private:
typedef std::vector< int > IntList;
typedef boost::unordered_map< int, IntList* > Map;
Map _map;
public:
class KeyIter : public boost::iterator_adaptor< KeyIter,
Map::const_iterator,
int,
boost::forward_traversal_tag,
int>
{
public:
KeyIter() : KeyIter::iterator_adaptor_() {}
private:
friend class DS;
friend class boost::iterator_core_access;
explicit KeyIter(Map::const_iterator it) : KeyIter::iterator_adaptor_(it) {}
explicit KeyIter(Map::iterator it) : KeyIter::iterator_adaptor_(it) {}
int dereference() const { return this->base()->first; }
};
class ValueIter : public boost::iterator_adaptor< ValueIter,
Map::const_iterator,
int,
boost::forward_traversal_tag,
int>
{
public:
ValueIter()
: ValueIter::iterator_adaptor_()
, _lIt()
{}
private:
friend class DS;
friend class boost::iterator_core_access;
explicit ValueIter(Map::const_iterator it)
: ValueIter::iterator_adaptor_(it)
, _lIt()
, _mIt(it)
{
IntList *pt = it->second; // <<-- issue here is I can't find if I've already reached the end of the map
if (pt) {
_lIt = it->second->begin();
}
}
int dereference() const { return *_lIt; }
void increment()
{
if (_lIt == _mIt->second->end()) {
++_mIt;
_lIt = _mIt->second->begin();
} else {
++_lIt;
}
}
IntList::iterator _lIt;
Map::const_iterator _mIt;
};
KeyIter beginKey() const { return KeyIter(_map.begin()); }
KeyIter endKey() const { return KeyIter(_map.end()); }
ValueIter beginValue() const { return ValueIter(_map.begin()); }
ValueIter endValue() const { return ValueIter(_map.end()); }
};
int main(int argc, char** argv)
{
DS ds;
std::vector< int > v1;
v1.push_back(10);
v1.push_back(30);
v1.push_back(50);
ds.add(90, v1);
std::vector< int > v2;
v2.push_back(20);
v2.push_back(40);
v2.push_back(60);
ds.add(120, v2);
std::cout << "------------ keys ---------------" << std::endl;
for (DS::KeyIter it = ds.beginKey(); it != ds.endKey(); ++it) {
std::cout << (*it) << std::endl;
}
std::cout << "------------ values ---------------" << std::endl;
// std::cout << (*(ds.beginValue())) << std::endl;
for (DS::ValueIter it = ds.beginValue(); it != ds.endValue(); ++it) {
std::cout << (*it) << std::endl;
}
return 0;
}
Implemented in c++11. You should be able to do the conversion to boost/c++03 fairly simply.
This iterator is FORWARD ONLY and it's quite fragile (see the comparison operator).
user discretion advised.
#include <iostream>
#include <vector>
#include <unordered_map>
typedef std::vector< int > IntList;
typedef std::unordered_map< int, IntList* > Map;
struct whole_map_const_iterator
{
using C1 = IntList;
using C2 = Map;
using I1 = C1::const_iterator;
using I2 = C2::const_iterator;
using value_type = I1::value_type;
using reference = I1::reference;
whole_map_const_iterator(I2 i2) : _i2(i2) {}
bool operator==(const whole_map_const_iterator& r) const {
if (_i2 != r._i2)
return false;
if (deferred_i1 && r.deferred_i1)
return true;
if (deferred_i1 != r.deferred_i1)
return false;
return _i1 == r._i1;
}
bool operator!=(const whole_map_const_iterator& r) const { return !(*this == r); }
reference operator*() const {
check_deferred();
return *_i1;
}
void check_deferred() const {
if (deferred_i1) {
_i1 = _i2->second->begin();
_i1limit = _i2->second->end();
deferred_i1 = false;
}
}
void go_next()
{
check_deferred();
if (++_i1 == _i1limit) {
++_i2;
deferred_i1 = true;
}
}
whole_map_const_iterator& operator++() {
go_next();
return *this;
}
whole_map_const_iterator operator++(int) {
auto result = *this;
go_next();
return result;
}
I2 _i2;
mutable I1 _i1 = {}, _i1limit = {};
mutable bool deferred_i1 = true;
};
IntList a { 1, 2, 3, 4, 5 };
IntList b { 6, 7, 8, 9, 10 };
Map m { { 1, &a }, { 2, &b } };
int main()
{
using namespace std;
auto from = whole_map_const_iterator(m.begin());
auto to = whole_map_const_iterator(m.end());
for ( ; from != to ; ++from) {
std::cout << *from << std::endl;
}
return 0;
}
example output:
6
7
8
9
10
1
2
3
4
5
For bonus points, answer this question:
Q: Why all that damn complication over the deferred flag?

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);
}