使用模板生成静态查找表

Using template to generate a static lookup table

本文关键字:静态 查找      更新时间:2023-10-16

我有:

const char kLetters[] = "QWERTYUIOPASDFGHJKLZXCVBNM";

我可以调用kLetters[n]在O(1)时间内获得键盘字母表的第n个字母。然而,我将不得不迭代kLetter(花费O(n)或至少O(log n))时间进行反向查找。

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

EDIT -正如评论中提到的,反向查找意味着我提供'E'并返回2。我的字母例子也不是最好的例子,我不想对顺序做任何假设。因此,我将字母改为键盘顺序。

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

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

好了,知道反向查找是什么之后,我想你可以这样做:

const char kLetters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
int get_index(char letter)
{
     return letter - 'A';
}

毕竟,字母A在索引0上,B在索引1上,C在索引2上……等等......这就足够了。


My O(1)解

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

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

  • 任意字母序列,如

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

    int Index(char letter);
    

下面是使用@ David Rodríguez在他的博客中描述的技术的完整代码:

#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:

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

看起来非常简单!

如果您可以使用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

假设'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'];
}

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

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]), "");

指定一个不存在的字符将导致错误。如果你在运行时使用这个函数,如果你指定了一个不存在的字符,你会得到未定义的行为。可以对这些情况进行适当的检查。

生成找到的第一个字符的索引。如果一个字符在haystack中出现两次,则不会给出错误。

如果您可以使用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强制编译时求值。

EDIT:这个解决方案是不正确的,正如所指出的。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;
};