2010-08-11 2 views
1

Может ли карта STL использоваться для ключей разного размера?Можно ли использовать карту 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() иметь дело с объединенными ключами.

Я полностью задумался об этом? Если это не орехи, есть ли у вас какие-либо предложения о том, как это сделать?

Off верхней части моей головы, мне нужно сделать унаследованный класс для ключа, когда базовый класс обеспечивает чистую виртуальную функцию для метода сортировки, а затем унаследовали основные классы для E_INT, E_CHAR и E_STR, которые реализуют sort() метод их использования. Тогда я бы использовал класс базового ключа в качестве ключа для карты.

Комментарии?


EDIT 8/13/2010

Я пытался несколько решений корректна, а также мои оригинальные мысли. Я продолжаю сталкиваться с проблемами. Я наткнулся на другую статью stackoverflow, в которой упоминалось type erasure, которое могло бы сделать трюк для моих разных ключей.


EDIT 8/16/2010

Добавлен ответ в разделе ответов ниже, что показывает кодированный решение, которое я реализован.

ответ

4

std::mapstrict weak ordering для ключей. Если вы можете применять один порядок для разных типов ключей с помощью специализированного компаратора, это не должно быть проблемой.

2

Я думаю, что ваш подход правильный с точки зрения того, что вы делаете с пользовательскими ключами.

При этом, если вы можете сэкономить накладные расходы на наличие N карт против одной карты с помощью специального ключа, я бы сказал, что это так, потому что это тривиально и быстро. Вы можете даже ленить загружать карты и просто скрывать реализацию за другим классом.

EDIT: ваш пользовательский компаратор должен быть простым. Вы можете строго заказать сначала перечисление, а затем для ключей с одинаковым значением перечисления (CHAR, INT, STR и т. Д.), Вы должны просто сравнить их значение. Это гарантировало бы заказы, необходимые для std :: map.

+0

Я хочу убедитесь, что я правильно понимаю ваш ответ. Я считаю, что вы говорите, могу ли я позволить себе несколько карт, а затем просто сделайте это, так как это быстро и просто. Так звучит так, что это случай, когда я слишком взволнован тем, что может сделать C++, и я пытался сделать слишком много;) –

+0

да, это правильно. –

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 каждой записи, чтобы увидеть, какой тип должен быть используемый.

+0

Предпочитайте 'boost :: variant' над' union', когда это возможно, для типов, которые он дает. –

1

Вам нужно будет обернуть ваши два ключа в один объект. Ваш новый класс также должен быть сопоставим. например:

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

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

Нет ничего прекращающего нажатия клавиш1 и key2 (умных) указателей. Как только объект (например, myKey) сопоставим с оператором <, его можно использовать на карте.

+0

Это не должно быть сопоставимо с помощью 'operator <'. Вы можете специализироваться на 'std :: less' для вашего типа или предоставить собственную функцию сравнения. –

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

Вы, вероятно, также потребуется конструктор копирования, но я думаю, вы получите идею. Как только вы сгладите морщины, это будет отлично работать как ключ карты. Честно говоря, решение вашего массива карт, вероятно, намного проще, если оно работает для вас.

+0

Предпочитайте 'boost :: variant' над' union', когда это возможно, для типов, которые он дает. У вас также есть утечки памяти из-за отсутствия правильного оператора присваивания ... –

+0

На самом деле выше, вероятно, произойдет сбой, потому что он будет делать побайтовые копии, а затем дважды удаляет указатель s. Я заметил, что реализация была неполной. –

2

Хотя есть было много решений представлены ... ни один из них не является столь же элегантно, как:

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

typedef std::map<key_type, value_type> map_type; 

Да, это все. A boost::variant естественно упорядочивается сначала по типу, а во втором (когда типы равны) на значения, которые они переносят (если их можно сравнить).

Я бросаю вызов любому, чтобы найти более простое решение;)

+1

Упрощенный? Представляем 'boost :: variant_map'! – GManNickG

+0

Я посмотрел на это, но, к сожалению, это ограничивает количество объектов, которые могут быть в варианте. В моей системе это ограничение равно 20. И хотя мой прототип выше очень прост и мал для понимания проблемы, у нас будет более 20 ключей уровня 1. –

+0

О, мой ... более 20 кажется много. Вы пытались переименовать 'BOOST_VARIANT_LIMIT_TYPES'? Если вы переопределите этот макрос, вы получите возможность использовать более 20 элементов. Несмотря на то, что для стольких элементов мне было бы интересно пойти по дороге абстракции, потому что я не хотел бы чередовать так много зависимостей. –

1

Если вы используете подталкивание :: Любой в сочетании с оператором сравнения, как в http://www.sgi.com/tech/stl/Map.html. Вы должны иметь возможность использовать несколько типов в качестве ключа, если ваш оператор может их заказать. Если вы используете boost :: Any, они будут занимать больше места, чем сам ключ, но остальные приведенные здесь решения также накладывают немного накладных расходов.

+0

Я читал о boost :: Any во время чтения стираний типа. Хотя я смог получить рабочий прототип без него. Спасибо за предложение. –

0

Решение Я Реализовано


консенсус в том, что это может быть сделано. Я как бы адаптировался к концепции type erasure, а также предложения от других выше. У меня есть что-то, что работает сейчас. Ключ карты должен быть объектом, который имеет указатель на полиморфный ключевой объект.

Я пробовал иметь только базовый объект в качестве типа ключа, но когда карта создает свою копию ключа, похоже, что он просто копировал базовый класс.

Так что я наивно переключился на указатель (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