tags:

views:

122

answers:

4

I have a few integer keys that is needed to retrieve a value. What is the most efficient way to store and retrieve the value based on the keys?

Currently I am converting and combining the three keys into a single string and store the key-value pair in a map. However, I think there should be a more efficient way of doing that based on the integer value alone for example generating a single key based on the three keys. Any ideas on that?

Thanks.

A: 

you can map a vector with the keys to the value with a map and use a lexicographical ordering for the map. If the number of keys is constant you can even map arrays of int with the same ordering

frag
+8  A: 

You can almost certainly do better than converting to a string (which is generally quite a slow operation). If your three items have small enough ranges, you can do some shifting and adding to put them together into a single integer with a larger range.

Lacking that, you can just create a struct that holds three ints, and defines operator< to compare them in a reasonable fashion:

class key { 
    int a, b, c;
public:
    bool operator<(key const &other) { 
        if (a < other.a)
            return true;
        if (a > other.a)
            return false;
        if (b < other.b)
            return true;
        if (b > other.b)
            return false;
        if (c < other.c)
            return true;
        return false;
    }
};

Edit: for those who care about it, using (std | tr1 | boost)::tuple wouldn't change the character of answer much, but would eliminate most of the code. A tuple is pretty similar to std::pair, except that it supports an arbitrary number of items instead of just two. For the example above, you could use something like:

#include <tuple>

namespace stdx = std::tr1;    // or stdx=boost::tr1, etc.

typedef stdx::tuple<int, int, int> key_t;

std::map<key_t, whatever> my_map;

tuple defines comparison operators automatically assuming the types are comparable (i.e., for comparing two tuples with the same number of elements, and the types of corresponding elements are comparable). In this case it does a lexicographic comparison (i.e., the result of the comparison is the result from the first pair of corresponding elements that are not equal -- of no such element exists, the two compare equal).

Jerry Coffin
Nice logic for comparator
Steve Townsend
This somewhat cries out for using a tuple.
sbi
I always implement mine with the `<` and `==` combination, breaking it down is way more readable... thanks !
Matthieu M.
@sbi: make it an answer, I'll upvote you should I come back on this post ;)
Matthieu M.
@Matthieu: Unfortunately I was "forced" to do C# before I had the chance to daily work with a compiler that comes with TR1, so I have no experience with `std::(tr1::)tuple`. `:(`And I hate giving answers when I'm not sure of how to do something. So if you know how to do this, go ahead and answer.
sbi
You don't have to use a tuple, You can use std::pair. Actually, you would use `std::pair<int, std::pair<int, int> >`!
TokenMacGuy
@Steve, yes it's nice but one simple mod would make it even nicer: replace the last 3 lines with `return (c < other.c);` I've used this idiom many times.
Mark Ransom
@Jerry: Nice to see you've tried my suggestion. The code is better if you don't have to manually implement `operator<`. Less clutter, less chance to make silly mistakes, less chance for others to comment on your style of implementing that operator. `:)` I wish I could up-vote your answer again.
sbi
@Token: Basically, this is what `std::tuple` is under the hood, only the API where you simply put the types into a list is much nicer than manually spelling out the compile-time recursion. If you have ever worked with type lists, you will appreciate this kind of syntactic sugar.
sbi
@Mark: The advantage of Jerry's code is that it is more easily extended when another data member is added. Sometimes uniformity wins over terseness.
sbi
@sbi: YAGNI. How often do you add another data member, and how hard will it be to fix it if you do? I still think my form is better, if only by the tiniest smidgen.
Mark Ransom
@Mark: `<shrug>` I'm not desperately trying to cling to one side of this argument. All I'm saying is that there's other POVs and that they might be valid, too. There's something to be said for symmetry besides it helping to maintain the code. Anyway, as I said, the real solution would be to use a type that does this itself: `std::tuple`.
sbi
A: 

What you are doing sounds fine. An alternative that you may be feeling your way towards would be to use a hash code generated from your three integer subkeys to access the values via something like unordered_map.

If the std::map is working for you and you don't need O(1) lookup time, moving to a hash-based container could introduce complexity in hash code generation (not easy to do well) that makes it more trouble than it is worth.

Steve Townsend
@Steve: for hash code generation from multiple sources use `boost::hash_combine`.
Matthieu M.
+2  A: 

Jerry Coffin's suggestion is a nice one, it is extensible to any number of keys of arbitrary width.

However, in practice, there are many situations where the following efficient method can be employed. If the size of the keys are such that their sum fits into a native type, then we can compose the key as following.

Suppose we have three key-parts:

a, 16-bit,
b, 32-bit
c, 16-bit

listed in the order of significance for comparison. (Same as in example of Jerry Coffin.)

Then, we can have one value

class key {
  private:
    uint64_t key_rep_;  // internal key representation
};

with the following underlying interpretation of key_rep_

AAAABBBBBBBBCCCC

Each letter above is a nibble, and shows the key-part it represents.

Comparing this approach with the straightforward one:

  • reading and writing key-part into the key are slower
  • comparing two keys are faster

In a map or set, comparison of keys are much more frequent than read/write, so this approach, when applicable, achieves overall efficiency.

ArunSaha
Note, however, that this method is mentioned (albeit in considerably less detail) in my answer: "If your three items have small enough ranges, you can do some shifting and adding to put them together into a single integer with a larger range."
Jerry Coffin
you dont mean 'their sum fits...'; you mean their bit-wise concatenation
pm100
sum of the size of keys, e.g. 16 + 32 + 16
ArunSaha
Things get a lot more complicated if any of those components are signed.
Mark Ransom