I want to store a floating point value for an unordered pair of an integers. I am unable to find any kind of easy to understand tutorials for this. E.g for the unordered pair {i,j} I want to store a floating point value f. How do I insert, store and retrieve values like this?
Simple way to handle unordered int pairs is using std::minmax(i,j) to generate std::pair<int,int>. This way you can implement your storage like this:
std::map<std::pair<int,int>,float> storage;
storage[std::minmax(i,j)] = 0.f;
storage[std::minmax(j,i)] = 1.f; //rewrites storage[(i,j)]
Admittedly proper hashing would give you some extra performance, but there is little harm in postponing this kind of optimization.
Here's some indicative code:
#include <iostream>
#include <unordered_map>
#include <utility>
struct Hasher
{
int operator()(const std::pair<int, int>& p) const
{
return p.first ^ (p.second << 7) ^ (p.second >> 3);
}
};
int main()
{
std::unordered_map<std::pair<int,int>, float, Hasher> m =
{ { {1,3}, 2.3 },
{ {2,3}, 4.234 },
{ {3,5}, -2 },
};
// do a lookup
std::cout << m[std::make_pair(2,3)] << '\n';
// add more data
m[std::make_pair(65,73)] = 1.23;
// output everything (unordered)
for (auto& x : m)
std::cout << x.first.first << ',' << x.first.second
<< ' ' << x.second << '\n';
}
Note that it relies on the convention that you store the unordered pairs with the lower number first (if they're not equal). You might find it convenient to write a support function that takes a pair and returns it in that order, so you can use that function when inserting new values in the map and when using a pair as a key for trying to find a value in the map.
Output:
4.234
3,5 -2
1,3 2.3
65,73 1.23
2,3 4.234
See it on ideone.com. If you want to make a better hash function, just hunt down an implementation of hash_combine (or use boost's) - plenty of questions here on SO explaining how to do that for std::pair<>s.
You implement a type UPair with your requirements and overload ::std::hash (which is the rare occasion that you are allowed to implement something in std).
#include <utility>
#include <unordered_map>
template <typename T>
class UPair {
private:
::std::pair<T,T> p;
public:
UPair(T a, T b) : p(::std::min(a,b),::std::max(a,b)) {
}
UPair(::std::pair<T,T> pair) : p(::std::min(pair.first,pair.second),::std::max(pair.first,pair.second)) {
}
friend bool operator==(UPair const& a, UPair const& b) {
return a.p == b.p;
}
operator ::std::pair<T,T>() const {
return p;
}
};
namespace std {
template <typename T>
struct hash<UPair<T>> {
::std::size_t operator()(UPair<T> const& up) const {
return ::std::hash<::std::size_t>()(
::std::hash<T>()(::std::pair<T,T>(up).first)
) ^
::std::hash<T>()(::std::pair<T,T>(up).second);
// the double hash is there to avoid the likely scenario of having the same value in .first and .second, resulinting in always 0
// that would be a problem for the unordered_map's performance
}
};
}
int main() {
::std::unordered_map<UPair<int>,float> um;
um[UPair<int>(3,7)] = 3.14;
um[UPair<int>(8,7)] = 2.71;
return 10*um[::std::make_pair(7,3)]; // correctly returns 31
}
Related
How to sort the multimap using comparator.
It should be reflected in the multimap container
#include <bits/stdc++.h>
using namespace std;
// Comparator function to sort pairs
// according to second value
bool cmp(pair<string, int>& a,
pair<string, int>& b)
{
return a.second < b.second;
}
// Function to sort the map according
// to value in a (key-value) pairs
void sort(multimap<string, int>& M)
{
// Declare vector of pairs
vector<pair<string, int> > A;
// Copy key-value pair from Map
// to vector of pairs
for (auto& it : M) {
A.push_back(it);
}
// Sort using comparator function
sort(A.begin(), A.end(), cmp);
// Print the sorted value
for (auto& it : A) {
cout << it.first << ' '
<< it.second << endl;
}
}
// Driver Code
int main()
{
// Declare Map
multimap<string, int> M;
// Given Map
M = { { "GfG", 3 },
{ "To", 2 },
{ "Welcome", 1 } };
// Function Call
sort(M);
cout<<"\n";
for(auto i:M)
{
cout<<i.first<<" "<<i.second<<endl;
}
return 0;
}
This is code I got from Geekforgeeks!
I totally understood the concept but I need to know how to sort the map itself if the map is multimap.?
OUTPUT
Welcome 1
To 2
GfG 3
GfG 3
To 2
Welcome 1
Basically the answer has been given in the comments already. I will summarize again and show an alternative.
The background is that we often want to use the properties of an associative container, but later sort it by its value and not by the key.
The unsorted associative containers, like std::unsorted_set, std::unordered_map , std::unordered_multiset and std::unordered_multimap cannot be ordered, as their names say. They are using a hash algorithm to store and retrieve their values.
Please read also in the CPP Reference about STL container.
And, if we look at the sorted associative container, we see that the std::set and the std::multiset do not work with key value pairs and the other two, the std::map and std::multimap have a key and value but are always sorted by their key.
But as said, we often want to have the advantages of an Associative container with a key - value pair and later some sorted-by-the-value data.
This can only be acieved by using 2 container having the needed property. In your above example the data is copied into a std::vector and then sorted. This mechanism can be used always, with any sortable container, by adding a std::pair of key and value to a container. So:
using Pair = std::pair<std::string, int>;
using Conatiner = std::vector<Pair>;
An often needed use case is counting something. For example, counting the letters in a string. This can be really easily achieved with a std::map or std::unordered_map and the sorted by the value using a 2nd container. Please see some example code below:
#include <iostream>
#include <string>
#include <utility>
#include <set>
#include <unordered_map>
#include <type_traits>
// ------------------------------------------------------------
// Create aliases. Save typing work and make code more readable
using Pair = std::pair<char, unsigned int>;
// Standard approach for counter
using Counter = std::unordered_map<Pair::first_type, Pair::second_type>;
// Sorted values will be stored in a multiset, because their may be double ranks
struct Comp { bool operator ()(const Pair& p1, const Pair& p2) const { return (p1.second == p2.second) ? p1.first<p2.first : p1.second>p2.second; } };
using Rank = std::multiset<Pair, Comp>;
// ------------------------------------------------------------
// Function to get the rank of letters in a string
Rank getRank(const std::string& str) {
Counter counter;
for (const char c : str) counter[c]++;
return { counter.begin(), counter.end() };
}
// Test / Driver Code
int main() {
if (std::string userString{}; std::getline(std::cin, userString))
for (const auto& [letter, count] : getRank(userString))
std::cout << (int)letter << ' ' << count << ' ';
}
So, we use the property of the std::unordred map as a fast associative container, not sorted by the key, in combination with a std::multiset which will act as a "sorter".
Or, you can take a std::multiset from the beginning. Example:
#include <iostream>
#include <string>
#include <utility>
#include <set>
// ------------------------------------------------------------
// Create aliases. Save typing work and make code more readable
using Pair = std::pair <std::string, int> ;
// Sorted values will be stored in a multiset
struct Comp { bool operator ()(const Pair& p1, const Pair& p2) const { return (p1.second == p2.second) ? p1.first<p2.first : p1.second<p2.second; } };
using Sorted = std::multiset<Pair, Comp>;
// ------------------------------------------------------------
// Driver Code
int main()
{
Sorted data { { "GfG", 3 }, { "To", 2 }, { "Welcome", 1 } };
for (const auto& [key, value] : data)
std::cout << key << '\t' << value << '\n';
}
Depends of course all on what you want to achieve . . .
So, I have a class
class table()
{
public:
string product, region, province ,price;
};
so i have an array of objects of the above shown class and my objective is to write a function with input as product, region, province the function searches this in the array and returns the price.
Now to search efficiently with least time I thought of using stl map in c++ but i require to have 3 keys in my map with price as the value,so I used this.
struct key
{
string product, region, province;
};
and my map definition is this.
unordered_map<key,string> mp;
Is this even practical to have three keys in map?
does having 3 keys affect the search time complexity for the map?
I am still pretty new to computer science field so please forgive me for any mistake in this question,
and also if i am completely wrong with the map idea a slight push in the right direction for me will be very helpful.
thankyou for your time.
if you are using map then you can use tuple<type1, type2....>
map<tuple<int, int , int> , string> foo;
foo.insert(make_tuple(1,2,3), "bar");
foo[make_tuple(1,3,1)] = "bar1";
foo[{2,2,3}] = "bar2";
c++ tuple
Yes you can do this with std::unordered_map, and it isn't as difficult as it may first seem. All a std::unordered_map cares about is:
The key type must be hashable using either a default hash algorithm or one provided to the container as a template-parameter type or a construction instance.
The key type must support equivalence checks (i.e. comparing two key instances for equivalence using either a custom "equals" type similar to the hasher template parameter argument, or the default std::equals, which uses operator == with two instances).
This is easier to see than to explain. Below is a trivial example:
#include <iostream>
#include <algorithm>
#include <string>
#include <cstdlib>
#include <tuple>
#include <unordered_map>
struct MyKey
{
struct Hash
{
// jacked up derivation of bernstiens string hasher
std::size_t operator()(MyKey const& key) const
{
std::size_t hash = 5381u;
for (auto c : key.s1)
hash = (hash << 5) + hash + c;
for (auto c : key.s2)
hash = (hash << 5) + hash + c;
for (auto c : key.s3)
hash = (hash << 5) + hash + c;
return hash;
}
};
std::string s1, s2, s3;
// equivalence determination.
bool operator ==(MyKey const& key) const
{
return std::tie(s1, s2, s3) == std::tie(key.s1, key.s2, key.s3);
}
};
int main()
{
std::unordered_map<MyKey, int, MyKey::Hash> ht;
ht.insert(std::make_pair<MyKey>({ "one", "two", "three" }, 42));
ht.insert(std::make_pair<MyKey>({ "four", "five", "six" }, 1999));
ht.insert(std::make_pair<MyKey>({ "seven", "eight", "nine" }, 1999));
auto it = ht.find(MyKey{ "one", "two", "three" });
if (it != ht.end())
std::cout << it->second << '\n';
it = ht.find(MyKey{ "four", "five", "six" });
if (it != ht.end())
std::cout << it->second << '\n';
it = ht.find(MyKey{ "seven", "eight", "nine" });
if (it != ht.end())
std::cout << it->second << '\n';
// shoudl NOT find anything
it = ht.find(MyKey{ "one", "three", "two" });
if (it != ht.end())
std::cout << it->second << '\n';
}
To answer probably the most important part of your question, no this does NOT affect search time. Obviously it takes longer to generate a hash value from three string than from one, but once that value is acquired the search mechanics are identical to any other unordered_map. If a collision is found, the equivalence check kicks in, and again, that's unavoidable, but not going affect the big-O complexity of your overall performance profile.
std::map is a touch easier to get working
struct key {
std::string product, region, province;
auto as_tie() const{
return std::tie(product, region, province);
}
friend bool operator<( key const& lhs, key const& rhs ){
return lhs.as_tie()<rhs.as_tie();
}
};
now key works as a key in a std::map. Here I let std tuple (via std tie) implement my < operator.
For an unordered map, you need to specialize std hash and provide == (or pass a hasher/equality function object to the template).
In C++20 you can just
auto operator<=>(key const&)const =default;
instead of the tie stuff and get map to work.
3 part keys don't change the big-O complexity of maps, ordered or not.
Unordered map still needs a custom hash.
Firstly, you'll want to be able to hash multiple key fields to provide one high quality hash value, and - in the style of boost hash_combine - you can do that with:
template <class T>
inline void hash_combine(std::size_t& seed, T const& v) {
seed ^= std::hash<T>()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
...combined with a general-purpose tuple hash function...
struct hash_tuple {
auto operator()(const auto& tuple) const {
std::size_t seed = 0;
std::apply([&](const auto& ...element){(..., hash_combine(seed, element));}, tuple);
return seed;
}
};
The above will work for any length of tuple, and any contained types that are themselves hashable.
You can then either use an unordered_map from a std::tuple of your key fields to the price double, as in:
using key = std::tuple<std::string, std::string, std::string>;
std::unordered_map<key, double, hash_tuple> m;
// ...then just use the map...
m[{"abc", "def", "ghi"}] = 3.14;
...or, you might want to have a struct with your keys and price combined...
struct X {
std::string a, b, c;
double price_;
auto tie_keys() const { return std::tie(a, b, c); }
bool operator==(const X& rhs) const {
return tie_keys() == rhs.tie_keys();
}
friend std::ostream& operator<<(std::ostream& os, const X& x) {
return os << x.a << ' ' << x.b << ' ' << x.c << ' ' << x.price_;
}
};
...and store those in an unordered_set<>...
auto hash_fn = [](const X& x) { return hash_tuple{}(x.tie_keys()); };
std::unordered_set<X, decltype(hash_fn)> m2;
// ...and use it...
m2.emplace("ab", "cde", "fg", 3.14);
m2.emplace("hij", "kl", "mn", 2.71);
for (const auto& x : m2)
std::cout << x << '\n';
You'll need various headers of course...
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <string>
#include <tuple>
does having 3 keys affect the search time complexity for the map?
Not if you have a decent hash function.
I have a requirement to implement a C++ class to support the following
Data:
key - string
subkey - string/double
Value - string/double
key & subkey together identifies the row uniquely.
Eg:
[ "key", "subkey", "value" ]
[ "cse", "a", 100 ]
[ "cse", "b", 120 ]
[ "cse", 100, 10 ]
Operations:
1) Given a key & return value
2) Given a key return an array of [ "subkey", "value" ]
The problem I'm facing is that subkey and value can be both double and string.
One way to solve this is by having a wrapper class which has the ability to store both double and string types.
The first level map will have a string as a the key and the value will be a map.
The second level map will have the key as the new wrapper class and value is also a new wrapper class.
Is this approach right ? or is there any better ways to do this ?
I hacked up a solution using Boost.Variant and C++11 unordered_map. The code is a quite nice example for C++11 in action.
You need to pay special attention to the combination of the two hashes
in the specialization of std::hash<key>::operator(), it can have a
strong impact on the quality of hashing. For a better implementation
have a look at boost::hash_combine, which sadly hasn't been
standardized.
In general what the code does: define a special key type that is
EqualityComparable and Hashable, then use it in a
std::unordered_map. You can build all this with just Boost and no
C++11 at all. If you have neither Boost or C++11, you are in a tight
spot. No real testing was done on this.
#include <boost/variant.hpp>
#include <string>
#include <functional>
#include <unordered_map>
#include <iostream>
struct key {
std::string primary;
boost::variant<std::string, double> secondary;
friend bool operator==(const key& x, const key& y)
{ return x.primary == y.primary && x.secondary == y.secondary; }
};
namespace std {
template<>
struct hash<key> {
std::size_t operator()(const key& k) const
{
std::size_t first = std::hash<std::string>()(k.primary);
std::size_t second;
// check for the more likely case first
if(const std::string* s = boost::get<std::string>(&(k.secondary))) {
second = std::hash<std::string>()(*s);
} else {
const double* d = boost::get<double>(&(k.secondary));
second = std::hash<double>()(*d);
}
return first ^ ( second << 1 ); // not so fancy hash_combine
}
};
} // std
int main()
{
typedef std::unordered_map<key, boost::variant<std::string, double>> MyMap;
MyMap m = {
{{"foo", "bar"}, "foobar"},
{{"foo", 23.0}, "foo23"},
{{"nothing", 23.0}, 23.0}
};
std::cout << m[{"foo", "bar"}] << std::endl;
std::cout << m[{"foo", 23.0}] << std::endl;
std::cout << m[{"nothing", 23.0}] << std::endl;
return 0;
}
The following wastes a little space per key, but has the advantage of being simple:
struct Key
{
Key(string primary, string subkey)
: primary(primary)
, is_double(false)
, string_subkey(subkey)
{}
Key(string primary, double subkey)
: primary(primary)
, is_double(true)
, double_subkey(subkey)
{}
string primary;
bool is_double;
double double_subkey;
string string_subkey;
}
You'll need to implement appropriate comparison operations and/or a hash function.
Python's itertools has tee for n-plicating iterables:
def tee(iterable, n=2):
it = iter(iterable)
deques = [collections.deque() for i in range(n)]
def gen(mydeque):
while True:
if not mydeque: # when the local deque is empty
newval = next(it) # fetch a new value and
for d in deques: # load it to all the deques
d.append(newval)
yield mydeque.popleft()
return tuple(gen(d) for d in deques)
I couldn't find the equivalent in Boost::Range. Am I missing something or should I just roll my own?
itertools.tee is well-suited for single pass iterables which are ubiquitous in Python. For instance Generators are single pass, and they are often used.
But if you already have list/deque you will not use itertools.tee for it, because it would involve superfluous duplication - you can just iterate over original list/deque over and over again.
C++ also has concept of single pass ranges, for instance Input Iterator, but they are not so ubiquitous. It is consequence of another set of aims of typical C++ program - give to user maximum as possible maintaining best performance. It is another mindset if you wish.
To illustrate this let's compare boost::transformed and itertools.imap (or generator expressions):
They both provide view of input sequence via given "prism". itertools.imap returns single pass iterable, while boost::transformed returns range view which has same category as input range - i.e., if you would pass Random Access Range as input you would get Random Access Range as the result.
Another fact is that C++ employs value semantics by default, while python has pointer semantics. It means that if copy iterator in C++, and "bump" it several times - original iterator will not be changed (though it can be invalidated if it is single pass range, but it is not the point).
But, sometimes you do want to accumulate values from single pass range and look at them several times. In such case, most common solution is to accumulate values to some container explicitly, by hands. For instance:
vector<int> cache(first,last);
However, tee-like wrappers are still possible in C++, here is proof-of-concept. Usage is:
auto forward_range = tee_range(first,last);
tee_range takes single pass range as argument and returns forward range (which is multi-pass) (there is also make_tee_iterator, which works at iterator level). So, you can take copies of that range and iterate it several times:
auto r = forward_range;
auto out = ostream_iterator<int>(cout," ");
copy(forward_range,out);
copy(forward_range,out);
copy(r,out);
Therer is also improvenment over itertools.tee - internally, only one deque is used to cache values.
live demo:
#include <boost/range/adaptor/transformed.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <boost/smart_ptr/make_shared.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/container/vector.hpp>
#include <boost/container/deque.hpp>
#include <boost/range/algorithm.hpp>
#include <algorithm>
#include <iterator>
#include <cassert>
#include <limits>
template<typename InputIterator>
class tee_iterator : public boost::iterator_facade
<
tee_iterator<InputIterator>,
const typename std::iterator_traits<InputIterator>::value_type,
boost::forward_traversal_tag
>
{
typedef typename std::iterator_traits<InputIterator>::value_type Value;
typedef unsigned Index;
struct Data
{
boost::container::deque<Value> values;
boost::container::vector<tee_iterator*> iterators;
InputIterator current,end;
Index min_index, current_index;
Index poped_from_front;
//
Data(InputIterator first,InputIterator last)
: current(first), end(last), min_index(0), current_index(0), poped_from_front(0)
{}
~Data()
{
assert(iterators.empty());
}
};
boost::shared_ptr<Data> shared_data;
Index index;
static Index get_index(tee_iterator *p)
{
return p->index;
}
public:
tee_iterator()
: index(std::numeric_limits<Index>::max())
{}
tee_iterator(InputIterator first,InputIterator last)
: shared_data(boost::make_shared<Data>(first,last)), index(0)
{
shared_data->iterators.push_back(this);
}
tee_iterator(const tee_iterator &x)
: shared_data(x.shared_data), index(x.index)
{
if(shared_data)
shared_data->iterators.push_back(this);
}
friend void swap(tee_iterator &l,tee_iterator &r)
{
using std::swap;
*boost::find(l.shared_data->iterators,&l) = &r;
*boost::find(r.shared_data->iterators,&r) = &l;
swap(l.shared_data,r.shared_data);
swap(l.index,r.index);
}
tee_iterator &operator=(tee_iterator x)
{
swap(x,*this);
}
~tee_iterator()
{
if(shared_data)
{
erase_from_iterators();
if(!shared_data->iterators.empty())
{
using boost::adaptors::transformed;
shared_data->min_index = *boost::min_element(shared_data->iterators | transformed(&get_index));
Index to_pop = shared_data->min_index - shared_data->poped_from_front;
if(to_pop>0)
{
shared_data->values.erase(shared_data->values.begin(), shared_data->values.begin()+to_pop);
shared_data->poped_from_front += to_pop;
}
}
}
}
private:
friend class boost::iterator_core_access;
void erase_from_iterators()
{
shared_data->iterators.erase(boost::find(shared_data->iterators,this));
}
bool last_min_index() const
{
return boost::count
(
shared_data->iterators | boost::adaptors::transformed(&get_index),
shared_data->min_index
)==1;
}
Index obtained() const
{
return Index(shared_data->poped_from_front + shared_data->values.size());
}
void increment()
{
if((shared_data->min_index == index) && last_min_index())
{
shared_data->values.pop_front();
++shared_data->min_index;
++shared_data->poped_from_front;
}
++index;
if(obtained() <= index)
{
++shared_data->current;
if(shared_data->current != shared_data->end)
{
shared_data->values.push_back(*shared_data->current);
}
else
{
erase_from_iterators();
index=std::numeric_limits<Index>::max();
shared_data.reset();
}
}
}
bool equal(const tee_iterator& other) const
{
return (shared_data.get()==other.shared_data.get()) && (index == other.index);
}
const Value &dereference() const
{
if((index==0) && (obtained() <= index))
{
shared_data->values.push_back(*(shared_data->current));
}
assert( (index-shared_data->poped_from_front) < shared_data->values.size());
return shared_data->values[index-shared_data->poped_from_front];
}
};
template<typename InputIterator>
tee_iterator<InputIterator> make_tee_iterator(InputIterator first,InputIterator last)
{
return tee_iterator<InputIterator>(first,last);
}
template<typename InputIterator>
boost::iterator_range< tee_iterator<InputIterator> > tee_range(InputIterator first,InputIterator last)
{
return boost::iterator_range< tee_iterator<InputIterator> >
(
tee_iterator<InputIterator>(first,last),
tee_iterator<InputIterator>()
);
}
// _______________________________________________________ //
#include <iostream>
#include <ostream>
#include <sstream>
int main()
{
using namespace std;
stringstream ss;
ss << "1 2 3 4 5";
istream_iterator<int> first(ss /*cin*/ ),last;
typedef boost::iterator_range< tee_iterator< istream_iterator<int> > > Range; // C++98
Range r1 = tee_range(first,last);
Range r2 = r1, r3 = r1;
boost::copy(r1,ostream_iterator<int>(cout," "));
cout << endl;
boost::copy(r2,ostream_iterator<int>(cout," "));
cout << endl;
boost::copy(r2,ostream_iterator<int>(cout," "));
}
Output is:
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
Boost.Spirit has Multi Pass iterator which has similar aims.
The multi_pass iterator will convert any input iterator into a forward iterator suitable for use with Spirit.Qi. multi_pass will buffer data when needed and will discard the buffer when its contents is not needed anymore. This happens either if only one copy of the iterator exists or if no backtracking can occur.
Consider a std::map<K,V>. I want to re-order the map by value profiting by an appropriate container std::C<V*> or std::C<V&>, in a way that no copies of values are done to store the elements in C. Furthermore, elements in C must be sorted according to the result of int f(V&) applied to each element. Despite my efforts I could not find an appropriate C and an enough efficient way to build it. Do you have any solution? A small example would be much appreciated.
Seems simple enough.
std::map<K,V> src;
int f(V&) {return 0;}
V* get_second(std::pair<const K,V> &r) {return &(r.second);} //transformation
bool pred(V* l, V* r) { return f(*l)<f(*r); } //sorting predicate
std::vector<V*> dest(src.size()); //make destination big enough
std::transform(src.begin(), src.end(), dest.begin(), get_second); //transformcopy
std::sort(dest.begin(), dest.end(), pred); //sort
Unless you meant C is supposed to be another map:
std::pair<K,V*> shallow_pair(std::pair<const K,V> &r)
{return std::pair<K,V*>(r.first, &(r.second));}
std::map<K, V*> dest2;
std::transform(src.begin(), src.end(),
std::inserter(dest2,dest2.end()), shallow_pair);
http://ideone.com/bBoXq
This requires the previous map to remain in scope longer than dest, and have no pairs removed until dest is destructed. Otherwise src will need to have been holding smart pointers of some sort.
You can use std::reference_wrapper, like this:
#include <map>
#include <string>
#include <algorithm>
#include <functional>
#include <prettyprint.hpp>
#include <iostream>
template <typename T>
std::ostream & operator<<(std::ostream & o, std::reference_wrapper<T> const & rw)
{
return o << rw.get();
}
int main()
{
std::map<int, std::string> m { { 1, "hello"}, { 2, "aardvark" } };
std::cout << m << std::endl;
std::vector<std::reference_wrapper<std::string>> v;
for (auto & p : m) v.emplace_back(p.second);
std::cout << v << std::endl;
std::sort(v.begin(), v.end(), std::less<std::string>); // or your own predicate
std::cout << v << std::endl;
v.front().get() = "world";
std::cout << m << std::endl;
}
This prints:
[(1, hello), (2, aardvark)]
[hello, aardvark]
[aardvark, hello]
[(1, hello), (2, world)]
Sounds like you are using multiple containers to represent multiple views into the same dataset. The trouble with this approach is in keeping the containers synchronized and avoiding dangling pointer issues. Boost.MultiIndex was made for just this purpose. A boost::multi_index container stores only one copy of each element, but allows you to access the elements via several indices.
Example:
#include <iterator>
#include <iostream>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/global_fun.hpp>
#include <boost/multi_index/member.hpp>
typedef std::string Key;
typedef int Value;
struct Record
{
Record(const Key& key, Value value) : key(key), value(value) {}
Key key;
Value value;
};
inline std::ostream& operator<<(std::ostream& os, const Record& rec)
{
os << rec.key << " " << rec.value << "\n";
return os;
}
inline int sortFunc(const Record& rec) {return -rec.value;}
struct ByNumber{}; // tag
namespace bmi = boost::multi_index;
typedef bmi::multi_index_container<
Record,
bmi::indexed_by<
// sort by key like a std::map
bmi::ordered_unique< bmi::member<Record, Key, &Record::key> >,
// sort by less<int> on free function sortFunc(const Record&)
bmi::ordered_non_unique<bmi::tag<ByNumber>,
bmi::global_fun<const Record&, int, &sortFunc> >
>
> RecordSet;
typedef RecordSet::index<ByNumber>::type RecordsByNumber;
int main()
{
RecordSet rs;
rs.insert(Record("alpha", -1));
rs.insert(Record("charlie", -2));
rs.insert(Record("bravo", -3));
RecordsByNumber& byNum = rs.get<ByNumber>();
std::ostream_iterator<Record> osit(std::cout);
std::cout << "Records sorted by key:\n";
std::copy(rs.begin(), rs.end(), osit);
std::cout << "\nRecords sorted by sortFunc(const Record&):\n";
std::copy(byNum.begin(), byNum.end(), osit);
}
Result:
Records sorted by key:
alpha -1
bravo -3
charlie -2
Records sorted by sortFunc(const Record&):
alpha -1
charlie -2
bravo -3
The place I'd start is to:
look at Boost::bimap
create ordering from V -> K via a comparator function object which evaluates f(V&) the way you suggested. (e.g. as in std::map<K,V,C> where C is a comparator class)
How about,
std::set<boost::shared_ptr<V>, compfunc>
where compfunc is a functor which takes two shared_ptr objects and applies the logic in your function?
Excuse the formatting, not good on my phone.
How about something like this (untested pseudo code):
V* g(pair<K,V> &v) { return &v.second; }
bool cmp(V* a, V* b) { return f(*a) < f(*b); }
map<K,V> map;
vector<V*> vec;
vec.reserve(map.size());
transform(map.begin(), map.end(), back_inserter(vec), g);
sort(vec.begin(), vec.end(), cmp);