繁体   English   中英

C ++元编程 - 编译时间搜索树

[英]C++ metaprogramming - compile time search tree

更新:抱歉令人困惑的术语 - 我不需要二叉树,但需要分段树或区间树。

想象一下,每次执行我的程序时,我都必须静态初始化一个搜索树。

Tree t;
t.add(10, 'Apple');
t.add(20, 'Pear');
t.add(50, 'Orange');
...
t.add(300, 'Cucumber');

..
// then I use it.
int key = 15;
String s = t.lookup(key) // Returns 'Apple' ( as the key is between 10 and 20)

树中的键和值是“静态的”,是硬编码的,但必须不时地进行维护。 是否存在元编程技巧如何在编译期间将键值组织成二进制搜索树(或跳过列表)?

例如,整个搜索树直接在代码.text实现,并且.data没有任何内容。 我还可以“预测”键的数量并提供订单。

我怀疑你是在这里从一个小山丘上建造一座山,这是因为: -

  • 您认为要在C ++中静态初始化某些内容,您必须在编译时执行此操作。

  • 无论你是不是用的的概念和下限 ,否则你不知道{上|下}不熟悉约束的v在[局部]有序序列S可以通过二进制搜索确定S ,而且你可以指望标准库至少有效地完成它。

我想你想有一个静态初始化数据结构映射整数键字符串文字,使得在运行时,你可以用一个整数查询它n ,非常有效地检索字符串文字s (如果有的话),其关键是最大的那个不大于n - 附加条件,大概是n不大于所有键。

如果这是正确的,那么您需要的静态初始化数据结构只是一个静态初始化 的整数 映射 M 到字符串文字 模板元编程不在框架中。

由于(假定的)条件是查询对于大于所有键的n都会失败,因此您需要在M包含一个标记值,其中键1大于您要查找的最大值。

然后,对于运行时整数n ,查询M以获取n的上限。 Mn的上限是大于n的最小键,如果有的话。 如果返回的迭代器itM.end()那么你有没有字符串n 否则,如果it == M.begin() ,那么每个键都大于n ,所以你再也没有n字符串。 否则,必须存在由--it定位的<key,value> ,并且该key必须是大于n的最大键。 所以n的字符串就是那个value

#include <map>

static const std::map<int,char const *> tab = 
{
    { 2,"apple" },
    { 5,"pear" },
    { 9,"orange" },
    { 14,"banana" },
    { 20,"plum" },
    { 20 + 1,nullptr }
};

const char * lookup(int n)
{
    auto it = tab.upper_bound(n);
    return it == tab.begin() || it == tab.end() ? nullptr : (--it)->second;
}

在此示例前面加上:

#include <iostream>

using namespace std;

int main(void)
{
    for (int i = 0; i <= 21; ++i) {
        cout << i;
        char const *out = lookup(i);
        cout << " -> " << (!out ? "Not found" : out) << endl;
    }
    return 0;
}

输出将是:

0 -> Not found
1 -> Not found
2 -> apple
3 -> apple
4 -> apple
5 -> pear
6 -> pear
7 -> pear
8 -> pear
9 -> orange
10 -> orange
11 -> orange
12 -> orange
13 -> orange
14 -> banana
15 -> banana
16 -> banana
17 -> banana
18 -> banana
19 -> banana
20 -> plum
21 -> Not found

现在这个程序中的tab是一个静态数据结构,但它没有在编译时初始化。 在调用main之前,它在程序的全局静态初始化中初始化 除非你要求程序启动时减少纳秒,否则我想不出为什么你需要在编译时初始化映射。

但是,如果你确实需要在编译时初始化它,它只是比这更有点小。 您需要将map作为constexpr对象,这意味着编译器可以在编译时构造它; 为此它必须是文字类型 ; 这意味着你不能使用std::map ,因为它不是文字类型。

因此,您将不得不使用:

constexpr std::pair<int,char const *> tab[] 
{
    { 2,"apple" },
    { 5,"pear" },
    { 9,"orange" },
    { 14,"banana" },
    { 20,"plum" },
    { 20 + 1,nullptr }
};   

或类似的,并以基本上所示的方式实现lookup(n) ,但在tab调用std::upper_bound 你可以在那里找到稍微有点笨拙的东西,如果你需要,我会留给你练习。

我终于创造了我想要实现的目标。 它过于复杂,看起来编译器优化器比我想的要聪明得多。

// Log "function"
template <int N>
struct LOG
{
    static const int value = LOG<N/2>::value + 1;
};
template<>
struct LOG<0>
{
    static const int value = 0;
};

// pow "function"
template <int N>
struct POW
{
    static const int value = POW<N-1>::value * 2;
};
template<>
struct POW<1>
{
    static const int value = 2;
};

template<>
struct POW<0>
{
    static const int value = 1;
};

// Pair <key, value> to be a payload in a type list
template<int k, char v>
struct Pair
{
    static const int key = k;
    static const int value = v;
};

// type list manipulator - access n-th element
template <size_t, class...> struct element;
template <class TT, class...TTs>
struct element<0, TT, TTs...>
{
    typedef TT type;
};
template <size_t K, class TT, class...TTs>
struct element<K, TT, TTs...>
{
    typedef typename element<K-1, TTs...>::type type;
};

template<class... Ts>
struct V;

// Binary split search algorithm (pure templatized)
template<class T, class... Ts>
struct V<T, Ts...> : private V<Ts...>
{
    template<size_t N = sizeof...(Ts), size_t level = LOG<sizeof...(Ts)+1>::value>
    struct impl
    {
        template<size_t IDX>
        inline static char search_impl(size_t n)
        {
            using namespace std;
            static const int depth = LOG<N>::value;
            static const int layer = depth - level;
            static const int key   = element<IDX, T, Ts...>::type::key;
            static const size_t left_idx  = IDX - ( N / POW<layer + 2>::value + 1);
            static const size_t right_idx =
                IDX + ( N / POW<layer + 2>::value + 1) > sizeof...(Ts) ?
                sizeof...(Ts) :
                IDX + ( N / POW<layer + 2>::value + 1);             

            //std::cout << setfill('*') << setw(layer) << ' '
            //    << "Level:" << level << " of:" << depth << std::endl
            //    << std::setw(layer) << ' ' 
            //    << "IDX/val/layer/POW/level: "
            //    << " " << IDX
            //    << "/" << key
            //    << "/" << layer
            //    << "/" << POW<layer>::value
            //    << "/" << level
            //    << "/" << left_idx
            //    << "/" << right_idx
            //    << std::endl;
            if ( n < key )
                return V<T, Ts...>::impl<N, level-1>::template search_impl<left_idx>(n);
            else
                return V<T, Ts...>::impl<N, level-1>::template search_impl<right_idx>(n);       
        }

    };

    template<size_t N>
    struct impl<N,1>
    {
        template<size_t IDX>
        inline static char search_impl(size_t n)
        {
            static const int key   = element<IDX, T, Ts...>::type::key;
            static const char value1 = element<IDX-1, T, Ts...>::type::value;
            static const char value2 = element<IDX, T, Ts...>::type::value;
            if ( n < key )
            {
                //std::cout << " *" << value1 << ' '  << IDX << std::endl;
                return value1;
            } else {
                //std::cout << " *" << value2 << ' '  << IDX << std::endl;
                return value2;
            }
        }
    };

    static void print()
    {
        std::cout << typeid(T).name() << ' ' << T::key << ' ' << (char)T::value << std::endl;
        V<Ts...>::print();
    }
    static char search(size_t n)
    {
        static const size_t level = LOG<sizeof...(Ts)+1>::value;
        static const size_t N = sizeof...(Ts);
        static const int height = LOG<N>::value;
        static const size_t root_idx = N / 2 + 1;
        static const int key = element<root_idx, T, Ts...>::type::key;
        //std::cout << "Level:" << level << " of:" << height << std::endl
        //    << "IDX/val: "
        //    << " " << root_idx
        //    << "/" << input[root_idx]
        //    << std::endl;

        static const size_t left_idx  = root_idx - ( N / POW<2>::value + 1);
        static const size_t right_idx = root_idx + ( N / POW<2>::value + 1);

        if( n < key)
            return V<T, Ts...>::impl<N, level-1>::template search_impl<left_idx>(n);
        else
            return V<T, Ts...>::impl<N, level-1>::template search_impl<right_idx>(n);
    }
};

template<>
struct V<>
{
    static void print()
    {}
};

int main(int argc, char *argv[])
{
    int i = std::stoi(argv[1]);

    typedef V<
    Pair<  0x1,'a'>,
    Pair< 0x11,'b'>,
    Pair< 0x21,'c'>,
    Pair< 0x31,'d'>,
    Pair< 0x41,'e'>,
    Pair< 0x51,'f'>,
    Pair< 0x61,'g'>,
    Pair< 0x71,'h'>,
    Pair< 0x81,'i'>,
    Pair< 0x91,'j'>,
    Pair<0x101,'k'>,
    Pair<0x111,'l'>,
    Pair<0x121,'m'>,
    Pair<0x131,'n'>,
    Pair<0x141,'o'>
    > TV;

    std::cout << (char)TV::search(i) << std::endl;

    return 0;
};

所以就是这样。 我的目标是“强制”编译器将所有常量放入代码中。 因为数据段中没有任何内容。 生成的代码将所有search_impl <*>方法一起内联,结果仅包含“cmp”和“jae”指令。 但是,如果要搜索的数组被定义为const static,看起来合理的编译器无论如何都会这样做。

我会用一个switch来进行查找。

编译器可以自由地使用跳转表,二进制搜索或任何其他技术来优化查找。 对于大多数切换表,编译器通常会发出最快的事情。

switch (key)
{
    case 10: return "Apple";
    case 20: return "Pear";
    ...
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM