How to implement a map with three keys in C++ STL? - c++

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.

Related

How to sort map using comparator with reflection in original map?

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 . . .

Compact way to write if(..) statement with many equalities

Is there a better way to write code like this:
if (var == "first case" or var == "second case" or var == "third case" or ...)
In Python I can write:
if var in ("first case", "second case", "third case", ...)
which also gives me the opportunity to easily pass the list of good options:
good_values = "first case", "second case", "third case"
if var in good_values
This is just an example: the type of var may be different from a string, but I am only interested in alternative (or) comparisons (==). var may be non-const, while the list of options is known at compile time.
Pro bonus:
laziness of or
compile time loop unrolling
easy to extend to other operators than ==
if you want to expand it compile time you can use something like this
template<class T1, class T2>
bool isin(T1&& t1, T2&& t2) {
return t1 == t2;
}
template<class T1, class T2, class... Ts>
bool isin(T1&& t1 , T2&& t2, T2&&... ts) {
return t1 == t2 || isin(t1, ts...);
}
std::string my_var = ...; // somewhere in the code
...
bool b = isin(my_var, "fun", "gun", "hun");
I did not test it actually, and the idea comes from Alexandrescu's 'Variadic templates are funadic' talk. So for the details (and proper implementation) watch that.
Edit:
in c++17 they introduced a nice fold expression syntax
template<typename... Args>
bool all(Args... args) { return (... && args); }
bool b = all(true, true, true, false);
// within all(), the unary left fold expands as
// return ((true && true) && true) && false;
// b is false
The any_of algorithm could work reasonably well here:
#include <algorithm>
#include <initializer_list>
auto tokens = { "abc", "def", "ghi" };
bool b = std::any_of(tokens.begin(), tokens.end(),
[&var](const char * s) { return s == var; });
(You may wish to constrain the scope of tokens to the minimal required context.)
Or you create a wrapper template:
#include <algorithm>
#include <initializer_list>
#include <utility>
template <typename T, typename F>
bool any_of_c(const std::initializer_list<T> & il, F && f)
{
return std::any_of(il.begin(), il.end(), std::forward<F>(f));
}
Usage:
bool b = any_of_c({"abc", "def", "ghi"},
[&var](const char * s) { return s == var; });
Alrighty then, you want Radical Language Modification. Specifically, you want to create your own operator. Ready?
Syntax
I'm going to amend the syntax to use a C and C++-styled list:
if (x in {x0, ...}) ...
Additionally, we'll let our new in operator apply to any container for which begin() and end() are defined:
if (x in my_vector) ...
There is one caveat: it is not a true operator and so it must always be parenthesized as it's own expression:
bool ok = (x in my_array);
my_function( (x in some_sequence) );
The code
The first thing to be aware is that RLM often requires some macro and operator abuse. Fortunately, for a simple membership predicate, the abuse is actually not that bad.
#ifndef DUTHOMHAS_IN_OPERATOR_HPP
#define DUTHOMHAS_IN_OPERATOR_HPP
#include <algorithm>
#include <initializer_list>
#include <iterator>
#include <type_traits>
#include <vector>
//----------------------------------------------------------------------------
// The 'in' operator is magically defined to operate on any container you give it
#define in , in_container() =
//----------------------------------------------------------------------------
// The reverse-argument membership predicate is defined as the lowest-precedence
// operator available. And conveniently, it will not likely collide with anything.
template <typename T, typename Container>
typename std::enable_if <!std::is_same <Container, T> ::value, bool> ::type
operator , ( const T& x, const Container& xs )
{
using std::begin;
using std::end;
return std::find( begin(xs), end(xs), x ) != end(xs);
}
template <typename T, typename Container>
typename std::enable_if <std::is_same <Container, T> ::value, bool> ::type
operator , ( const T& x, const Container& y )
{
return x == y;
}
//----------------------------------------------------------------------------
// This thunk is used to accept any type of container without need for
// special syntax when used.
struct in_container
{
template <typename Container>
const Container& operator = ( const Container& container )
{
return container;
}
template <typename T>
std::vector <T> operator = ( std::initializer_list <T> xs )
{
return std::vector <T> ( xs );
}
};
#endif
Usage
Great! Now we can use it in all the ways you would expect an in operator to be useful. According to your particular interest, see example 3:
#include <iostream>
#include <set>
#include <string>
using namespace std;
void f( const string& s, const vector <string> & ss ) { cout << "nope\n\n"; }
void f( bool b ) { cout << "fooey!\n\n"; }
int main()
{
cout <<
"I understand three primes by digit or by name.\n"
"Type \"q\" to \"quit\".\n\n";
while (true)
{
string s;
cout << "s? ";
getline( cin, s );
// Example 1: arrays
const char* quits[] = { "quit", "q" };
if (s in quits)
break;
// Example 2: vectors
vector <string> digits { "2", "3", "5" };
if (s in digits)
{
cout << "a prime digit\n\n";
continue;
}
// Example 3: literals
if (s in {"two", "three", "five"})
{
cout << "a prime name!\n\n";
continue;
}
// Example 4: sets
set <const char*> favorites{ "7", "seven" };
if (s in favorites)
{
cout << "a favorite prime!\n\n";
continue;
}
// Example 5: sets, part deux
if (s in set <string> { "TWO", "THREE", "FIVE", "SEVEN" })
{
cout << "(ouch! don't shout!)\n\n";
continue;
}
// Example 6: operator weirdness
if (s[0] in string("014") + "689")
{
cout << "not prime\n\n";
continue;
}
// Example 7: argument lists unaffected
f( s, digits );
}
cout << "bye\n";
}
Potential improvements
There are always things that can be done to improve the code for your specific purposes. You can add a ni (not-in) operator (Add a new thunk container type). You can wrap the thunk containers in a namespace (a good idea). You can specialize on things like std::set to use the .count() member function instead of the O(n) search. Etc.
Your other concerns
const vs mutable : not an issue; both are usable with the operator
laziness of or : Technically, or is not lazy, it is short-circuited. The std::find() algorithm also short-circuits in the same way.
compile time loop unrolling : not really applicable here. Your original code did not use loops; while std::find() does, any loop unrolling that may occur is up to the compiler.
easy to extend to operators other than == : That actually is a separate issue; you are no longer looking at a simple membership predicate, but are now considering a functional fold-filter. It is entirely possible to create an algorithm that does that, but the Standard Library provides the any_of() function, which does exactly that. (It's just not as pretty as our RLM 'in' operator. That said, any C++ programmer will understand it easily. Such answers have already been proffered here.)
Hope this helps.
First, I recommend using a for loop, which is both the easiest and
most readable solution:
for (i = 0; i < n; i++) {
if (var == eq[i]) {
// if true
break;
}
}
However, some other methods also available, e.g., std::all_of, std::any_of, std::none_of (in #include <algorithm>).
Let us look at the simple example program which contains all the above keywords
#include <vector>
#include <numeric>
#include <algorithm>
#include <iterator>
#include <iostream>
#include <functional>
int main()
{
std::vector<int> v(10, 2);
std::partial_sum(v.cbegin(), v.cend(), v.begin());
std::cout << "Among the numbers: ";
std::copy(v.cbegin(), v.cend(), std::ostream_iterator<int>(std::cout, " "));
std::cout << '\\n';
if (std::all_of(v.cbegin(), v.cend(), [](int i){ return i % 2 == 0; }))
{
std::cout << "All numbers are even\\n";
}
if (std::none_of(v.cbegin(), v.cend(), std::bind(std::modulus<int>(),
std::placeholders::_1, 2)))
{
std::cout << "None of them are odd\\n";
}
struct DivisibleBy
{
const int d;
DivisibleBy(int n) : d(n) {}
bool operator()(int n) const { return n % d == 0; }
};
if (std::any_of(v.cbegin(), v.cend(), DivisibleBy(7)))
{
std::cout << "At least one number is divisible by 7\\n";
}
}
You may use std::set to test if var belongs to it. (Compile with c++11 enabled)
#include <iostream>
#include <set>
int main()
{
std::string el = "abc";
if (std::set<std::string>({"abc", "def", "ghi"}).count(el))
std::cout << "abc belongs to {\"abc\", \"def\", \"ghi\"}" << std::endl;
return 0;
}
The advantage is that std::set<std::string>::count works in O(log(n)) time (where is n is number of strings to test) comparing to non compact if witch is O(n) in general. The disadvantage is that construction of the set takes O(n*log(n)). So, construct it once, like:
static std::set<std::string> the_set = {"abc", "def", "ghi"};
But, IMO it would be better to leave the condition as is, unless it contains more than 10 strings to check. The performance advantages of using std::set for such a test appears only for big n. Also, simple non compact if is easier to read for average c++ developer.
The closest thing would be something like:
template <class K, class U, class = decltype(std::declval<K>() == std::declval<U>())>
bool in(K&& key, std::initializer_list<U> vals)
{
return std::find(vals.begin(), vals.end(), key) != vals.end();
}
We need to take an argument of type initializer_list<U> so that we can pass in a braced-init-list like {a,b,c}. This copies the elements, but presumably we're going doing this because we're providing literals so probably not a big deal.
We can use that like so:
std::string var = "hi";
bool b = in(var, {"abc", "def", "ghi", "hi"});
std::cout << b << std::endl; // true
If you have access to C++14 (not sure if this works with C++11) you could write something like this:
template <typename T, typename L = std::initializer_list<T>>
constexpr bool is_one_of(const T& value, const L& list)
{
return std::any_of(std::begin(list), std::end(list), [&value](const T& element) { return element == value; });
};
A call would look like this:
std::string test_case = ...;
if (is_one_of<std::string>(test_case, { "first case", "second case", "third case" })) {...}
or like this
std::string test_case = ...;
std::vector<std::string> allowedCases{ "first case", "second case", "third case" };
if (is_one_of<std::string>(test_case, allowedCases)) {...}
If you don't like to "wrap" the allowed cases into a list type you can also write a little helper function like this:
template <typename T, typename...L>
constexpr bool is_one_of(const T& value, const T& first, const L&... next) //First is used to be distinct
{
return is_one_of(value, std::initializer_list<T>{first, next...});
};
This will allow you to call it like this:
std::string test_case = ...;
if (is_one_of<std::string>(test_case, "first case", "second case", "third case" )) {...}
Complete example on Coliru
Worth noting that in most Java and C++ code I've seen, listing 3 or so conditionals out is the accepted practice. It's certainly more readable than "clever" solutions. If this happens so often it's a major drag, that's a design smell anyway and a templated or polymorphic approach would probably help avoid this.
So my answer is the "null" operation. Just keep doing the more verbose thing, it's most accepted.
You could use a switch case. Instead of having a list of separate cases you could have :
include
using namespace std;
int main ()
{
char grade = 'B';
switch(grade)
{
case 'A' :
case 'B' :
case 'C' :
cout << "Well done" << endl;
break;
case 'D' :
cout << "You passed" << endl;
break;
case 'F' :
cout << "Better try again" << endl;
break;
default :
cout << "Invalid grade" << endl;
}
cout << "Your grade is " << grade << endl;
return 0;
}
So you can group your results together: A, B and C will output "well done".
I took this example from Tutorials Point:
http://www.tutorialspoint.com/cplusplus/cpp_switch_statement.htm

C++ storing a value in an unordered pair

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
}

Implement a C++ which supports 2 level looks with keys of any type

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.

How can I order a map by value efficiently?

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