C++ storing a value in an unordered pair
Asked Answered
R

3

11

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?

Reminiscence answered 21/3, 2015 at 7:23 Comment(8)
you need to specialize std::hash for the type you want to use as the key, and you also need to define operator== on it. And that's it.Telega
@TheParamagneticCroissant Can you post an answer please? I have never worked with hash, etc, so please keep it simple as possible. Thank you.Reminiscence
there's already a canonical answer on specializing std::hash for your custom key type here.Telega
@TheParamagneticCroissant I didn't understand a bit. :-/ I have no idea what hash is or what it does.Reminiscence
std::map<std::pair<int,int>, float> should work without specialization.Esperanto
By unordered do you mean that {1, 2} and {2, 1} are considered identical?Road
@JonathanPotter to use std::map<std::pair<int,int>, float>, user would have to define his own hash functionAssort
@Assort std::map uses less, not hashEsperanto
S
16

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.

Sluice answered 14/8, 2016 at 12:55 Comment(0)
F
1

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.

Featurelength answered 24/3, 2015 at 16:3 Comment(0)
A
1

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
}
Albertinaalbertine answered 24/3, 2015 at 16:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.