使用模板生成静态查找表

时间:2011-09-14 15:41:38

标签: c++ templates

我有:

const char kLetters[] = "QWERTYUIOPASDFGHJKLZXCVBNM";

我可以调用kLetters[n]在O(1)时间内获取键盘字母的第n个字母。但是,我将不得不遍历kLetter(取O(n)或至少O(log n))时间进行反向查找。

我想使用模板创建一个反向查找表作为编译时静态查找表,并且想知道是否有这样做的方法。

编辑 - 如评论中所述,反向查找意味着我提供'E'并返回2.此外,我的字母表示例不是最好的示例,我不想对订单做任何假设。因此我将字母表更改为键盘顺序。

6 个答案:

答案 0 :(得分:5)

这样的事情怎么样?它允许您指定范围而不是完整的字符串。

#include <iostream>

template <int Start, int End, int N>
struct lookup {
        static_assert(Start != End, "Can't have 0 length lookup table");
        enum { value =  lookup<Start+(Start < End ? 1:-1),End,N-1>::value };
};

template <int Start, int End>
struct lookup<Start,End,0> {
        enum { value = Start };
};

template <int Start, int End, int V, int P=0>
struct reverse_lookup {
        static_assert(Start != End, "V isn't in the range Start, End");
        static_assert(Start != End || !P, "Can't have 0 length range");
        enum { value = reverse_lookup<Start+(Start < End ? 1:-1),End,V,P+1>::value };
};

template <int Start, int End, int P>
struct reverse_lookup<Start,End,Start,P> {
        enum { value = P };
};

int main() {
   std::cout << char(lookup<'A', 'Z', 3>::value) << std::endl;
   std::cout << char(lookup<'Z', 'A', 3>::value) << std::endl;
   std::cout << int(reverse_lookup<'A','Z','F'>::value) << std::endl;
}

答案 1 :(得分:3)

好的,在知道反向查询后,我认为你可以这样做:

const char kLetters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

int get_index(char letter)
{
     return letter - 'A';
}

毕竟,字母A位于0索引,B位于1C位于2 ......所以上。这提供了足够的暗示。


我的O(1)解决方案

到目前为止,其他解决方案适用于非任意字母序列,而@awoodland解决方案假定要获取其索引的字母在编译时已知使它不那么有用。

但是这个解决方案试图解决这两个限制;也就是说,它应该有效:

  • 使用任意字母序列,例如

       const char Letters[] = "ZBADCEWFVGHIUXJTKSLYQMROPN";
    
  • 编译时字母可能是 unknown 。获取索引的函数具有以下签名:

    int Index(char letter);
    

以下是使用@DavidRodríguez在his blog中描述的技术的完整代码:

#include <iostream>

const char Letters[] = "ZBADCEWFVGHIUXJTKSLYQMROPN";

template<char L> int Index();

template<> int Index<'Z'>() { return 0; }
template<> int Index<'B'>() { return 1; }
template<> int Index<'A'>() { return 2; }
template<> int Index<'D'>() { return 3; }
template<> int Index<'C'>() { return 4; }
template<> int Index<'E'>() { return 5; }
template<> int Index<'W'>() { return 6; }
template<> int Index<'F'>() { return 7; }
template<> int Index<'V'>() { return 8; }
template<> int Index<'G'>() { return 9; }
template<> int Index<'H'>() { return 10; }
template<> int Index<'I'>() { return 11; }
template<> int Index<'U'>() { return 12; }
template<> int Index<'X'>() { return 13; }
template<> int Index<'J'>() { return 14; }
template<> int Index<'T'>() { return 15; }
template<> int Index<'K'>() { return 16; }
template<> int Index<'S'>() { return 17; }
template<> int Index<'L'>() { return 18; }
template<> int Index<'Y'>() { return 19; }
template<> int Index<'Q'>() { return 20; }
template<> int Index<'M'>() { return 21; }
template<> int Index<'R'>() { return 22; }
template<> int Index<'O'>() { return 23; }
template<> int Index<'P'>() { return 24; }
template<> int Index<'N'>() { return 25; }

typedef int (*fptr)();
const int limit = 26;
fptr indexLookup[ limit ];

template <char L>
struct init_indexLookup {
    static void init( fptr *indexLookup ) {
        indexLookup[ L - 'A' ] = &Index<L>;
        init_indexLookup<L-1>::init( indexLookup );
    }
};

template <>
struct init_indexLookup<'A'> {
    static void init( fptr *indexLookup ) {
        indexLookup[ 0 ] = &Index<'A'>;
    }
};

const int ignore = (init_indexLookup<'Z'>::init(indexLookup),0);

int Index(char letter)
{
    return indexLookup[letter-'A']();
}

这是测试代码:

int main()
{
    std::cout << Index('A') << std::endl;
    std::cout << Index('Z') << std::endl;
    std::cout << Index('B') << std::endl;
    std::cout << Index('K') << std::endl;
}

输出:

2
0
1
16

在线演示:http://ideone.com/uzE2t

嗯,这实际上是两个函数调用:一个到Index(),另一个来自indexLookup中的一个。您可以通过编写(ideone

轻松避免第一次函数调用
int main()
{
    std::cout << indexLookup['A'-'A']() << std::endl;
    std::cout << indexLookup['Z'-'A']() << std::endl;
    std::cout << indexLookup['B'-'A']() << std::endl;
    std::cout << indexLookup['K'-'A']() << std::endl;
}

这看起来很麻烦,但是嘿,我们可以Index()内联:

inline int Index(char letter)
{
    return indexLookup[letter-'A']();
}

看起来很好,现在很可能编译器会使它等同于一个函数调用!


简单但O(1)解决方案

等待。我刚刚意识到整个解决方案简化为一个初始化为:

的查找表
 const int indexLookup[] = {2,1,4,3,5,7,9,10,11,14,16,18,21,
                            25,23,24,20,22,17,15,12,8,6,13,19,0};

 inline int Index(char letter)
 {
       return indexLookup[letter-'A'];
 }

看起来简直令人难以置信!

答案 2 :(得分:1)

如果你可以使用Boost并且只需要编译时查找:

using namespace boost::mpl;
typedef vector_c<char, 'A', 'B', 'C', 'D'> Chars;   

// lookup by index:
std::cout << at_c<Chars, 1>::type::value << std::endl; // B 

// lookup by value:
typedef find<Chars, integral_c<char, 'C'> >::type Iter;
std::cout << Iter::pos::value << std::endl; // 2

答案 3 :(得分:0)

这假定'Z'> 'A',但不假设字母是连续的。 (虽然如果它们需要更少的内存)我很想放入if (numrLetters>26)条件,所以一个聪明的编译器可以使用加法而不是表格用于ASCII,但后来决定我不想放慢代码不太聪明的编译器。

const char kLetters[] = "ABCDEFGHJJKLMNOPQRSTUVWXYZ";
const int numLetters = sizeof(kLetters);
const char rkLetters['Z'-'A'] = {};
const int numrLetters = sizeof(rkLetters);
struct LetterInit {
    LetterInit() {
        for(int i=0; i<numLetters; ++i)
            rkLetters[kLetters[i]-'A'] = i;
    }
}LetterInitInst;

char findChar(int index) {
    assert(index>=0 && index<numLetters);
    return kLetters[index];
}
int findIndex(char letter) { 
    assert(letter>='A' && letter<='Z');
    return rkLetters[letter-'A'];
}

答案 4 :(得分:0)

由于有几个解决方案不生成表但仍允许编译时查找,这里是另一个

constexpr char kLetters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
constexpr int get(char const x, int const i = 0) {
  return kLetters[i] == x ? i : get(x, i + 1);
}

在编译时使用

int x[get('F')];
static_assert(sizeof(x) == sizeof(int[5]), "");

指定不存在的字符将导致错误。如果在运行时使用该函数,则在指定不存在的字符时将获得未定义的行为。可以为这些情况添加适当的检查。

它产生找到的第一个字符的索引。如果角色在大海捞针中出现两次,则不会给出错误。

答案 5 :(得分:-1)

如果您可以使用c ++ 0x(使用gcc 4.5测试),则可以使用:

#include<initializer_list>
#include<iostream>
#include<map>
constexpr int getLetterNumber(char a){ return std::map<char,int>({{'a',2},{'b',1},{'c',4}})[a]; }
int main(){
    const char ch='b';
    std::cout<<ch<<": "<<getLetterNumber(ch)<<std::endl;
}

constexpr在编译时执行评估。

编辑:正如所指出的,该解决方案不正确。 constexpr不会影响编译时评估。这确实在编译时进行查找(类似于同时发布的解决方案)。

#include<iostream>
template<char C> int ch2Num();
#define CHR(c,i) template<> int ch2Num<c>(){ return i; }
CHR('a',2); CHR('b',1); /* ... */
#undef CHR
int main(void){
    const char ch='b';
    std::cout<<ch<<": "<<ch2Num<ch>()<<std::endl;
};