可以将STL映射与不同大小的键一起使用

时间:2010-08-11 17:01:31

标签: c++ stl map

STL地图可以用于不同大小的密钥吗?

我没有此代码。我仍在试图弄清楚是否可以这样做,因此我的问题。 (我是那种可以在一个不可能解决的问题上花费太多时间的人。我希望从你的智慧中学习。)

我正在研究一个基本上有两个键的查找表。数字类型键和类型特定的辅助键。

例如,第一级键是枚举:

enum key_type {
  E_ERROR = 0,
  E_INT   = 1,
  E_CHAR  = 2,
  E_STR   = 3,
}; // Yes I know you don't HAVE to specify the values for the enumeration

然后辅助键取决于key_type。 E_INT的辅助键是整数,E_CHAR是字符等。

Key: E_INT
2ndary Key Examples: 1, 2, 3, 4

Key: E_CHAR
2ndary Key Examples: 'a', 'b', 'c', 'd'

Key: E_STR
2ndary Key Examples: "abc", "xyz", "pdq", "jrr"

我的第一反应是使这个地图指针数组。第一级键用作数组的索引。数组索引指向支持辅助键类型的映射。

+--------+
| E_INT  |------------------------------>+------------------+
+--------+                               | MAP with INT key |
| E_CHAR |---------------\               +------------------+
+--------+                \
| E_STR  |------\          \---->+-------------------+
+--------+       \               | MAP with CHAR key |
                  \              +-------------------+
                   \
                    \------>+------------------+
                            | MAP with STR key |
                            +------------------+

知道我可以让上述工作正常工作,但我想我可以将这两个键组合在一起并使用一个自定义sort()算法来处理组合密钥。

考虑到这一点,我完全疯了吗?如果不是坚果,你对如何处理这个有什么建议吗?

在我的脑海中,我需要为key创建一个继承类,其中基类为sort方法提供纯虚函数,然后继承E_INT的{​​{1}},{{1}和} E_CHAR,实现E_STR方法的用法。然后我会使用基本类作为地图的关键。

评论


编辑8/13/2010

我一直在尝试提出一些解决方案,以及我原来的想法。我一直在遇到问题。我偶然发现了另一篇提到type erasure的stackoverflow文章,该文章可能为我的不同密钥提供了技巧。


编辑2010年8月16日

在下面的答案部分添加了一个答案,其中显示了我实施的编码解决方案。

8 个答案:

答案 0 :(得分:4)

std::map要求密钥strict weak ordering。如果您可以使用自定义比较器对不同的键类型强制执行单个订单,那么它应该不是问题。

答案 1 :(得分:2)

我认为您的方法在使用自定义密钥方面是正确的。

那就是说,如果你可以节省N个地图与一个地图和自定义键的开销,我会说这样做,因为它很简单而且很快。你甚至可以懒得加载地图,只是将实现隐藏在另一个类后面。

编辑:您的自定义比较器也应该很简单。您可以先通过枚举严格排序,然后对于具有相同枚举值(CHAR,INT,STR等)的键,您应该只按值进行比较。这将保证std :: map所需的排序。

答案 2 :(得分:2)

虽然提出了许多解决方案......但没有一个像以下一样优雅:

typedef boost::variant<int,char,std::string> key_type;

typedef std::map<key_type, value_type> map_type;
是的,就是这样。 boost::variant首先按类型排序,第二种(当类型相同时)按其携带的值排序(如果可以比较)。

我挑战任何人找到一个更简单的解决方案;)

答案 3 :(得分:1)

您需要将两个键包装成一个对象。您的新课程也需要具有可比性。例如:

struct myKey
{
  int key1;
  std::string key2;

  bool operator<(const myKey&) const;
}; 

没有什么能阻止key1和key2成为(智能)指针。一旦一个对象(如myKey)通过&lt;运算符,它可以在地图中使用。

答案 4 :(得分:1)

如果将boost :: Any与http://www.sgi.com/tech/stl/Map.html中的比较运算符结合使用。只要您的操作员可以订购它们,您就应该能够使用多种类型作为密钥。如果你使用boost :: Any,它们将占用比密钥本身更多的空间,但是这里显示的其余解决方案也会产生一些开销。

答案 5 :(得分:0)

您是否必须将所有列表排在一起?如果是这样,那么当您尝试访问元素时,您的方法将会很痛苦。

一种愚蠢的方法是简单地在联合上创建联合类型和比较函数:

typedef struct IntRecord {
    key_type key;
    int record;
};

typedef struct CharRecord {
    key_type key;
    char record;
};

typedef struct StrRecord {
    key_type key;
    const char * record;
};

typedef struct Base {
    key_type key;
};
typedef union Record {
    Base b;
    IntRecord i;
    CharRecord c;
    StrRecord s;
};

现在你的比较函数可以查看每个记录的b.key以查看应该使用的类型。

答案 6 :(得分:0)

您可以为实现<运算符的键实现一个类,类似这样(未经测试):

struct UnionMapKey {
    int key_type;
    union {
        Error *err; // maybe pointer because complex types can't be in C unions
        int i;
        char c;
        string *s; // pointer because complex types can't be in C unions
    };
    UnionMapKey(const string &stringContent) { key_type = E_STR; s = new string(stringContent); }
    // other constructor overrides
    bool operator<(const UnionMapKey &rhs) {
        if (key_type != rhs.key_type) return key_type < rhs.key_type;
        if (key_type == E_ERROR) return err < rhs.err;
        // etc.
    }
    ~UnionMapKey() {
        if (key_type == E_STR) delete s;
    }
};

你可能还需要一个复制构造函数,但我认为你明白了。一旦你解决皱纹,这将作为地图键正常工作。老实说,如果适用于您的地图阵列解决方案可能会更简单。

答案 7 :(得分:0)

我实施的解决方案


共识是可以做到的。我对type erasure概念以及上面其他人的建议进行了改编。我有一些现在有用的东西。 map键必须是一个具有指向多态键对象的指针的对象。

我尝试只使用基础对象作为键类型,但是当地图创建其键的副本时,看起来它只是复制基类。

所以我天真地切换到指针(key_base_c *)。然而,这只是做指针比较。我甚至没有使用我的分类。

阅读type erasure信息后。我将我的指针解决方案放在一个multi_key_c对象内,该对象将其<==strIdx()调用转发到我隐藏在key_base_c内的#include <map> #include <sstream> #include <iostream> #include <utility> // // list of types to act as the primary key. The primary key dicatates the // format of the secondary key. // enum e_types { E_ERROR = 0, E_INT = 1, E_CHAR = 2, E_STR = 3, }; // Base class for the multi-key. class key_base_c { public: key_base_c (enum e_types key_type) : key1(key_type) {}; virtual ~key_base_c(void) {}; virtual std::string strIdx (void) const { std::stringstream ss_idx; ss_idx << key1; return (ss_idx.str()); } virtual bool operator< (const key_base_c &b) const { return (key_base_c::operator<(&b)); } virtual bool operator< (const key_base_c *p) const { return (key1 < p->key1); } virtual bool operator== (const key_base_c &b) const { return (key_base_c::operator==(&b)); } virtual bool operator== (const key_base_c *p) const { return (key1 == p->key1); } protected: e_types key1; // the primary key }; // template policy_key_c // // EVENT_TYPE_VAL - select the enumerated value to use for key1's value // // KEY2_TYPE - select the class to use for the second key. For built // in types they use their default < and == operators, // If a private custom type is specified then it must // have its own < and == operators specified // template <enum e_types EVENT_TYPE_VAL, class KEY2_TYPE> class policy_key_c : public key_base_c { public: policy_key_c (KEY2_TYPE key_value) : key_base_c(EVENT_TYPE_VAL), key2(key_value) {}; virtual ~policy_key_c(void) {}; // return the index as a string. virtual std::string strIdx (void) const { std::stringstream ss_idx; ss_idx << key_base_c::strIdx() << "." << key2; return (ss_idx.str()); } // // operator < // virtual bool operator< (const key_base_c &b) const { return (operator<(&b)); } virtual bool operator< (const key_base_c *p) const { // if the primary key is less then it's less, don't check 2ndary if (key_base_c::operator<(p)) { return (true); } // if not less then it's >=, check if equal, if it's not equal then it // must be greater if (!(key_base_c::operator==(p))) { return (false); } // primary keys are equal, so now check the 2ndary key. Since the // primary keys are equal then that means this is either a key_base_c // object or its a policy_key_c object. const policy_key_c *p_other = dynamic_cast<const policy_key_c*>(p); // if NULL then it was a key_base_c, and has no secondary key, so it is // lexigraphically smaller than us, ergo we are not smaller than it. if (!p_other) { return (false); } return (key2 < p_other->key2); } // // operator == // virtual bool operator== (const key_base_c &b) const { return(operator==(&b)); } virtual bool operator== (const key_base_c *p) const { // if the primary key isn't equal, then we're not equal if (!(key_base_c::operator==(p))) { return (false); } // primary key is equal, so now check the secondary key. Since the // primary keys are equal, then that means this is eitehr a key_base_c // object or its a policy_key_c object. const policy_key_c *p_other = dynamic_cast<const policy_key_c*>(p); // if NULL then it was a key_base_c if (!p_other) { // why? If the LHS is a key_base_c it doesn't go any deeper than // the base. Hence we don't either. return (true); } return (key2 == p_other->key2); } protected: KEY2_TYPE key2; // The secondary key. }; class multi_key_c { public: multi_key_c (key_base_c *p) : p_key(p) {}; ~multi_key_c(void) {}; bool operator< (const multi_key_c &mk) const { return (p_key->operator<(mk.p_key)); } bool operator== (const multi_key_c &mk) const { return (p_key->operator==(mk.p_key)); } std::string strIdx (void) const { return (p_key->strIdx()); } protected: key_base_c *p_key; }; // DO_TEST(x, op, y) // x, y: can be any derived key type // op : The operation to do < or == // // Prints the operation being done along with the results of the operation // For example: // DO_TEST(a, <, b) // will print: // a < b: <results> // // where <results> are the results of the operation 'a < b' #define DO_TEST(x, op, y, expect) \ { \ bool retval = x op y; \ std::cout << #x " " #op " " #y ": " << retval \ << " = " << ((retval == expect) ? "pass" : "----FAIL") << "\n"; \ } template <class C> void print_them (C **pp_c, int count, std::string s_type) { int idx; std::cout << "\n" << count << " keys for " << s_type << "\n"; for (idx = 0 ; idx < count; ++idx) { std::cout << " " << (*pp_c)->strIdx() << "\n"; pp_c++; } } int main (void) { std::cout << "\nBASE\n"; key_base_c base_error(E_ERROR), base_int(E_INT), base_char(E_CHAR); key_base_c base_str(E_STR); key_base_c *key_base_array[] = { &base_error, &base_int, &base_char, &base_str }; print_them(key_base_array, (sizeof(key_base_array) / sizeof(key_base_array[0])), "key_base_c"); DO_TEST(base_error, < , base_error, false); DO_TEST(base_error, < , base_int, true); DO_TEST(base_int, < , base_char, true); DO_TEST(base_char, < , base_str, true); std::cout << "\n"; DO_TEST(base_error, ==, base_error, true); DO_TEST(base_int, ==, base_int, true); DO_TEST(base_char, ==, base_char, true); DO_TEST(base_str, ==, base_str, true); std::cout << "\n"; DO_TEST(base_error, ==, base_int, false); DO_TEST(base_int, ==, base_char, false); DO_TEST(base_char, ==, base_str, false); // INT // typedef policy_key_c<E_INT, int> key_int_2; key_int_2 i1(1), i2(2), i3(3), i4(4); key_int_2 *key_int2_array[] = { &i1, &i2, &i3, &i4, }; print_them(key_int2_array, (sizeof(key_int2_array) / sizeof(key_int2_array[0])), "key_int_2"); DO_TEST(base_int, < , i1, false); DO_TEST(i1, < , base_int, false); DO_TEST(i1, < , base_char, true); DO_TEST(base_char, < , i1, false); DO_TEST(i1, ==, i1, true); DO_TEST(i1, ==, base_int, true); DO_TEST(base_int, ==, i1, true); DO_TEST(i1, ==, base_error, false); DO_TEST(base_error, ==, i1, false); std::cout << "\n"; DO_TEST(i1, < , i2, true); DO_TEST(i1, < , i3, true); DO_TEST(i1, < , i4, true); // CHAR typedef policy_key_c<E_CHAR, char> key_char_c; key_char_c c1('a'), c2('b'), c3('c'), c4('d'); key_char_c *key_char_array[] = { &c1, &c2, &c3, &c4, }; print_them(key_char_array, (sizeof(key_char_array) / sizeof(key_char_array[0])), "key_char"); DO_TEST(base_int, < , c1, true ); DO_TEST(base_int, ==, c1, false); DO_TEST(base_char, < , c1, false); DO_TEST(base_char, ==, c1, true ); DO_TEST(base_str, < , c1, false); DO_TEST(base_str, ==, c1, false); std::cout << "\n"; DO_TEST(c1, < , c1, false); DO_TEST(c1, ==, c1, true ); DO_TEST(c1, < , c2, true ); DO_TEST(c1, ==, c2, false); std::cout << "\n"; DO_TEST(c1, ==, i1, false); DO_TEST(i1, ==, c1, false); DO_TEST(c1, < , i1, false); DO_TEST(i1, < , c1, true ); // STR typedef policy_key_c<E_STR, std::string> key_str_c; key_str_c s1("aaa"), s2("bbb"), s3("ccc"), s4("ddd"); key_str_c *key_str_array[] = { &s1, &s2, &s3, &s4 }; print_them(key_str_array, (sizeof(key_str_array) / sizeof(key_str_array[0])), "key_str"); DO_TEST(base_int, < , s1, true ); DO_TEST(base_char, < , s1, true ); DO_TEST(base_str, < , s1, false); DO_TEST(base_str, ==, s1, true ); DO_TEST(s1, < , base_int, false); DO_TEST(s1, < , base_char, false); DO_TEST(s1, < , base_str, false); DO_TEST(s1, ==, base_str, true); std::cout << "\n"; DO_TEST(s1, < , s1, false); DO_TEST(s1, ==, s1, true ); DO_TEST(s1, < , s2, true ); DO_TEST(s1, ==, s2, false); std::cout << "\n\nNOW TESTING THE MAP\n\n"; typedef std::multimap<multi_key_c, std::string> multiKeyMap; multiKeyMap myMap; multi_key_c k1(&i1), k2(&i2), k3(&i3), k4(&i4); multi_key_c k5(&c1), k6(&c2), k7(&c3), k8(&c4); multi_key_c k9(&s1), k10(&s2), k11(&s3), k12(&s4); myMap.insert(std::make_pair(k1, "one")); myMap.insert(std::make_pair(k2, "two")); myMap.insert(std::make_pair(k3, "three")); myMap.insert(std::make_pair(k4, "four")); myMap.insert(std::make_pair(k1, "one.2")); myMap.insert(std::make_pair(k4, "four.2")); myMap.insert(std::make_pair(k5, "c1")); myMap.insert(std::make_pair(k5, "c1.2")); myMap.insert(std::make_pair(k6, "c2")); myMap.insert(std::make_pair(k6, "c2.2")); myMap.insert(std::make_pair(k7, "c3")); myMap.insert(std::make_pair(k8, "c4")); myMap.insert(std::make_pair(k9, "s1")); myMap.insert(std::make_pair(k10, "s2")); myMap.insert(std::make_pair(k11, "s3")); myMap.insert(std::make_pair(k12, "s4")); myMap.insert(std::make_pair(k12, "s4.2")); myMap.insert(std::make_pair(k11, "s3.2")); myMap.insert(std::make_pair(k10, "s2.2")); myMap.insert(std::make_pair(k9, "s1.2")); multiKeyMap::iterator pos; for (pos = myMap.begin(); pos != myMap.end(); ++pos) { std::cout << pos->first.strIdx() << " : " << pos->second <<"\n"; } return (0); } 指针它

在编写了几个派生类之后,我很快发现这有助于成为一个模板,我的解决方案很快就会落实到位。

我怀疑可能有更好的方法来实现这一点,但这是我到目前为止所做的:

BASE

4 keys for key_base_c
    0
    1
    2
    3
base_error < base_error: 0 = pass
base_error < base_int: 1 = pass
base_int < base_char: 1 = pass
base_char < base_str: 1 = pass

base_error == base_error: 1 = pass
base_int == base_int: 1 = pass
base_char == base_char: 1 = pass
base_str == base_str: 1 = pass

base_error == base_int: 0 = pass
base_int == base_char: 0 = pass
base_char == base_str: 0 = pass

4 keys for key_int_2
    1.1
    1.2
    1.3
    1.4
base_int < i1: 0 = pass
i1 < base_int: 0 = pass
i1 < base_char: 1 = pass
base_char < i1: 0 = pass
i1 == i1: 1 = pass
i1 == base_int: 1 = pass
base_int == i1: 1 = pass
i1 == base_error: 0 = pass
base_error == i1: 0 = pass

i1 < i2: 1 = pass
i1 < i3: 1 = pass
i1 < i4: 1 = pass

4 keys for key_char
    2.a
    2.b
    2.c
    2.d
base_int < c1: 1 = pass
base_int == c1: 0 = pass
base_char < c1: 0 = pass
base_char == c1: 1 = pass
base_str < c1: 0 = pass
base_str == c1: 0 = pass

c1 < c1: 0 = pass
c1 == c1: 1 = pass
c1 < c2: 1 = pass
c1 == c2: 0 = pass

c1 == i1: 0 = pass
i1 == c1: 0 = pass
c1 < i1: 0 = pass
i1 < c1: 1 = pass

4 keys for key_str
    3.aaa
    3.bbb
    3.ccc
    3.ddd
base_int < s1: 1 = pass
base_char < s1: 1 = pass
base_str < s1: 0 = pass
base_str == s1: 1 = pass
s1 < base_int: 0 = pass
s1 < base_char: 0 = pass
s1 < base_str: 0 = pass
s1 == base_str: 1 = pass

s1 < s1: 0 = pass
s1 == s1: 1 = pass
s1 < s2: 1 = pass
s1 == s2: 0 = pass


NOW TESTING THE MAP

1.1 : one
1.1 : one.2
1.2 : two
1.3 : three
1.4 : four
1.4 : four.2
2.a : c1
2.a : c1.2
2.b : c2
2.b : c2.2
2.c : c3
2.d : c4
3.aaa : s1
3.aaa : s1.2
3.bbb : s2
3.bbb : s2.2
3.ccc : s3
3.ccc : s3.2
3.ddd : s4
3.ddd : s4.2

此输出为:

{{1}}