位掩码选择矢量/设置元素?

时间:2016-08-22 15:16:20

标签: c++ vector set bitmask

目的

我正在尝试使用容器(例如vector,set等)提取元素,但不是使用索引,而是使用位掩码技术。

情景:

vector<string> alphabets {"a", "b", "c", "d", "e"};

测试用例:

  1. 输入:5(等效位掩码:00101

    输出:新向量{"c", "e"}

  2. 输入13(位掩码:01101

    输出向量:{"b", "c", "e"}

  3. 天真的工作解决方案:

    vector<string*> extract(int mask){
        vector<string*> result;
        bitset<n> bits(mask);
        for (int j = 0; j < n; ++j) {
            if (bits[j]){
                result.push_back(&alphabets[j]);
            }
        }
    }
    

    进一步改进

    • 时间复杂度
    • 空间复杂性
    • 概念/想法??
    • API可用性??

    示例用例。

    排列a,b,c,d,e的所有组合,其中a,b,c,d,e被包裹在容器上。 (Generating combinations in c++问题中已提及其他方法。)

    #include <vector>
    #include <iostream>
    #include <algorithm>
    #include <string>
    #include <bitset>
    
    using namespace std;
    
    
    int main(){
        const int n = 5;
    
        vector<string> alphabets {"a", "b", "c", "d", "e"};
    
        for ( int i = 0; i < pow(2, n); ++i){
            vector<string*> result;
            bitset<n> bits(i);
            for (int j = 0; j < n; ++j) {
                if (bits[j]){
                    result.push_back(&alphabets[j]);
                }
            }
    
            for (auto r: result){
                cout << *r;
            }
            cout << endl;
        }
        return 0;
    }
    

2 个答案:

答案 0 :(得分:2)

如果你赞成表现优于可读性,我认为这是一个合理的起点。

尝试1

基本上我避免任何内存分配。

#include <string>
#include <vector>
#include <bitset>
#include <iostream>
#include <iterator>
#include <tuple>
#include <array>


template<class From, std::size_t N>
auto
select(From const& from, std::bitset<N> const& bits)
{
    std::array<const std::string*, N> result { nullptr };
    auto i = std::begin(result);
    std::size_t found;
    std::size_t count = found = bits.count();
    std::size_t index = 0;
    while (count)
    {
        if (bits.test(index)) {
            *i++ = &from[index];
            --count;
        }
        ++index;
    }
    return std::make_tuple(found, result);
}

int main()
{

    std::vector<std::string> alphabet = { "a", "b", "c", "d", "e", "f", "g", "h" };

    for (unsigned x = 0 ; x < 256 ; ++x)
    {
        auto info = select(alphabet, std::bitset<8>(x));
        auto ptrs = std::get<1>(info).data();
        auto size = std::get<0>(info);
        while(size--)
        {
            std::cout << *(*ptrs++) << ", ";
        }

        std::cout << '\n';
    }
}

尝试2 - 运行时查找的大小为纳秒......

这里我在编译时预先计算所有可能的字母表。

运行时当然是快速的。但是,超过14个字符的字母表可能需要一段时间才能编译......

更新:警告!当我将字母大小设置为16 clang以消耗32GB内存时,停止桌面上的所有其他应用程序,并且在我可以执行任何其他操作之前需要重新启动我的macbook。你被警告了。

#include <string>
#include <vector>
#include <bitset>
#include <iostream>
#include <iterator>
#include <tuple>
#include <array>


template<class From, std::size_t N>
auto
select(From const& from, std::bitset<N> const& bits)
{
    std::array<const std::string*, N> result { nullptr };
    auto i = std::begin(result);
    std::size_t found;
    std::size_t count = found = bits.count();
    std::size_t index = 0;
    while (count)
    {
        if (bits.test(index)) {
            *i++ = &from[index];
            --count;
        }
        ++index;
    }
    return std::make_tuple(found, result);
}

template<std::size_t Limit>
struct alphabet
{
    constexpr alphabet(std::size_t mask)
    : size(0)
    , data { }
    {
        for (std::size_t i = 0 ; i < Limit ; ++i)
        {
            if (mask & (1 << i))
                data[size++] = char('a' + i);
        }
    }

    std::size_t size;
    char data[Limit];

    friend decltype(auto) operator<<(std::ostream& os, alphabet const& a)
    {
        auto sep = "";
        for (std::size_t i = 0 ; i < a.size; ++i)
        {
            std::cout << sep << a.data[i];
            sep = ", ";
        }
        return os;
    }
};

template<std::size_t Limit>
constexpr alphabet<Limit> make_iteration(std::size_t mask)
{
    alphabet<Limit> result { mask };
    return result;
}

template<std::size_t Limit, std::size_t...Is>
constexpr auto make_iterations(std::index_sequence<Is...>)
{
    constexpr auto result_space_size = sizeof...(Is);
    std::array<alphabet<Limit>, result_space_size> result
    {
        make_iteration<Limit>(Is)...
    };
    return result;
}

template<std::size_t Limit>
constexpr auto make_iterations()
{
    return make_iterations<Limit>(std::make_index_sequence<std::size_t(1 << Limit) - 1>());
}

int main()
{
    static constexpr auto alphabets = make_iterations<8>();
    for(const auto& alphabet : alphabets)
    {
        std::cout << alphabet << std::endl;
    }
}

答案 1 :(得分:1)

那么,那么你的例子可以更短(更短到一点,它可能没有展示你真正需要的东西)(编辑:我计划将字符直接输出到cout,完全不使用result向量,然后我忘了它,当我重写代码时......所以它仍然与你的相似):

#include <vector>
#include <iostream>
#include <string>

int main() {
    const std::vector<std::string> alphabets {"a", "b", "c", "d", "e"};
    const unsigned N = alphabets.size();
    const unsigned FULL_N_MASK = 1 << N;

    for (unsigned mask = 0; mask < FULL_N_MASK; ++mask) {

        std::vector<const std::string*> result;
        unsigned index = 0, test_mask = 1;
        while (index < N) {
            if (mask & test_mask) result.push_back(&alphabets[index]);
            ++index, test_mask <<= 1;
        }

        for (auto r : result) std::cout << *r;
        std::cout << std::endl;
    }
    return 0;
}

这让我有点奇怪,为什么你需要从掩码中提取result向量,也许你只能使用mask本身,并获取内部循环内的特定字符串(就像我在构建result)时一样。

我的代码中的主要更改是省略bitset<n> bits(i);初始化,因为您已经在i中使用了本地位,可以使用C语言按位运算符(<< >> & ^ | ~)轻松访问,其中&# 39;不需要再次使用bitset将它们转换为相同的内容。

如果bitset太大而无法使掩码适合某些常规类型(如n),那么

uint32_t用法就有意义了。即便如此,对于相当小的固定n,我可能会使用很少的64/128 / 256b无符号整数(目标平台上可用的内容)。

关于速度:当然,您无法击败++maskstd::next_permutation将比单个本地机器代码指令慢,即使它以32/64以下的大小以相同的方式实现。

但问题是,如果您可以围绕该位掩码构建算法,则可以有效地利用该优势。

许多国际象棋引擎使用各种棋盘值的位掩码编码,轻松检查某些领域是否被占用,或者在一个步骤中进行一些初步的转弯可用性检查,例如让所有可以在下一轮获得对手数字的棋子:{ {1}} - 5个本机CPU按位运算符,如果结果为0,则表示您的棋子无法攻击任何一个东西。或者你有可以采取的对手棋子的位掩码。将其与所有作品的幼稚循环进行比较,并检查对手棋子的[+ -1,+ 1]字段,并将其添加到attacking_pawns = (board_opponent & (my_pawns<<9) & valid_left_forward_pawn_take_mask) | (board_opponent & (my_pawns<<7) & valid_right_forward_pawn_take_mask); // for the side going "up" on board等动态分配的内存中。

或者是否存在能够通过提前退出来修剪组合树的算法,完全跳过一些实质性的排列子集,这可能比完全vector扫描更快。