在Boost多索引容器中搜索位字段数据

时间:2016-06-12 14:24:19

标签: c++ boost bit-fields boost-multi-index

我正在寻找从多索引容器中检索基于位字段的对象的最佳解决方案。为简单起见,数据为:

enum Bit
{
    b0 = 1,
    b1 = 2,
    b2 = 4
};

struct Item
{
    int field; // contains Bit values
    int info;
};

据我所知,可能有两种可能性:

  1. 在equal_range调用中使用自定义谓词(此示例无效,因为我不知道如何定义谓词):

    struct MyTag;
    
    using boost::multi_index_container;
    using namespace boost::multi_index;
    
    typedef composite_key<Item,   
        member<Item, int, &Item::field>,
        member<Item, int, &Item::info> > CompositeKey;
    typedef hashed_non_unique< tag<MyTag>, CompositeKey> Index1;
    typedef boost::multi_index_container<Item, indexed_by<Index1> > Container;
    
    struct Predicate: public Index1::pred_type
    {
        /// what to put here?
    };
    
    void f()
    {
        Item item1 = { int( b0 | b1), 123};
        Item item2 = { int( b1 | b2), 123};
        Item item3 = { int( b2), 123};
    
        Container c;
        c.insert( item1);
        c.insert( item2);
        c.insert( item3);
    
        auto result = c.get<MyTag>().equal_range( boost::make_tuple( int( b2), 123), Index1::hash_type(), Predicate());
        for( auto i = result.first; i != result.second; ++i)
        {
            std::cout << i->field << ' ';
            // expect item2 and item3
        }
    }
    
  2. 在枚举中为每个位添加多索引容器中的Item和索引中的访问器:

    struct Item
    {
        int field;
        int info;
    
        bool isB0() const { return ( field & b0) != 0; }
        bool isB1() const { return ( field & b1) != 0; }
        bool isB2() const { return ( field & b2) != 0; }
    };
    
    
    struct MyTag1;
    struct MyTag2;
    struct MyTag3;
    
    using boost::multi_index_container;
    using namespace boost::multi_index;
    
    typedef composite_key<Item,   
        const_mem_fun<Item, bool, &Item::isB0>,
        member<Item, int, &Item::info> > CompositeKeyB0;
    typedef composite_key<Item,   
        const_mem_fun<Item, bool, &Item::isB1>,
        member<Item, int, &Item::info> > CompositeKeyB1;
    typedef composite_key<Item,   
        const_mem_fun<Item, bool, &Item::isB2>,
        member<Item, int, &Item::info> > CompositeKeyB2;
    typedef hashed_non_unique< tag<MyTag1>, CompositeKeyB0> Index1;
    typedef hashed_non_unique< tag<MyTag2>, CompositeKeyB1> Index2;
    typedef hashed_non_unique< tag<MyTag3>, CompositeKeyB2> Index3;
    typedef boost::multi_index_container<Item, indexed_by<Index1, Index2, Index3> > Container;
    
    void f()
    {
        Item item1 = { int( b0 | b1), 123};
        Item item2 = { int( b1 | b2), 123};
        Item item3 = { int( b2), 123};
    
        Container c;
        c.insert( item1);
        c.insert( item2);
        c.insert( item3);
    
        auto result = c.get<MyTag2>().equal_range( boost::make_tuple( true, 123));
        for( auto i = result.first; i != result.second; ++i)
        {
            std::cout << i->field << ' ';
            // expect item2 and item3
        }
    }
    

3 个答案:

答案 0 :(得分:1)

我认为考虑这个问题的方法是:

&#34;如何在没有提升的情况下创建这些索引?&#34;

这将是这样的:

#include <unordered_map>
#include <list>
#include <functional>
#include <iostream>

enum Bit
{
    b0 = 1,
    b1 = 2,
    b2 = 4
};

struct Item
{
    int field;
    int info;

    bool isB0() const { return ( field & b0) != 0; }
    bool isB1() const { return ( field & b1) != 0; }
    bool isB2() const { return ( field & b2) != 0; }
};

template<int bit>
struct ItemHash
{
  bool operator()(const Item& item) const {
    auto accum = std::hash<int>()(item.info);
    if ((item.field & bit) == bit)
      accum += 1234567;
    return accum;
  }
};

template<int bit>
  struct ItemEqual
  {
    bool operator()(const Item& l, const Item& r) const 
    {
      auto lt = std::make_tuple((l.field & bit) == bit, l.info);
      auto rt = std::make_tuple((r.field & bit) == bit, r.info);
      return lt == rt;
    }
  };

struct Items
{
  Item& insert(Item item)
  {
    // store object
    auto i = _storage.insert(_storage.end(), item);

    // update indeces
    _by_b0.emplace(item, *i);
    _by_b1.emplace(item, *i);
    _by_b2.emplace(item, *i);

    _by_b2_and_b1.emplace(item, *i);

    return *i;
  };



  // object storage
  std::list<Item> _storage;

  // indexes we want to keep
  std::unordered_map<Item, std::reference_wrapper<Item>, ItemHash<b0>, ItemEqual<b0> > _by_b0;
  std::unordered_map<Item, std::reference_wrapper<Item>, ItemHash<b1>, ItemEqual<b1> > _by_b1;
  std::unordered_map<Item, std::reference_wrapper<Item>, ItemHash<b2>, ItemEqual<b2> > _by_b2;

  // multiple bits?
  std::unordered_map<Item, std::reference_wrapper<Item>, ItemHash<b2|b1>, ItemEqual<b2|b1> > _by_b2_and_b1;
};


int main()
{
    Item item1 = { int( b0 | b1), 123};
    Item item2 = { int( b1 | b2), 123};
    Item item3 = { int( b2), 123};

    Items c;
    c.insert( item1);
    c.insert( item2);
    c.insert( item3);

    auto result = c._by_b2.equal_range( Item { b2, 123 });
    for( auto i = result.first; i != result.second; ++i)
    {
        std::cout << i->second.get().field << ' ' << i->second.get().info << std::endl;
        // expect item2 and item3
    }
}

看着这一点,我们开始意识到field中的这些位实际上是属性。我们真的想在每个属性组合上创建索引吗?只需将我们的Item存储在vector中,并在for循环中使用谓词,这真的会更有效,更容易维护吗?

例如:

#include <vector>
#include <algorithm>
#include <iostream>
#include <tuple>

enum Bit
{
    b0 = 1,
    b1 = 2,
    b2 = 4
};

struct Item
{
    int field;
    int info;

    bool isB0() const { return ( field & b0) != 0; }
    bool isB1() const { return ( field & b1) != 0; }
    bool isB2() const { return ( field & b2) != 0; }
};

struct matches_bits_and_info {

    constexpr matches_bits_and_info(int bits, int info) : _bits(bits), _info(info) {}

    bool operator()(const Item& item) const {
        return std::make_tuple((item.field & _bits) , item.info)
        == std::tie(_bits, _info);
    }
    int _bits, _info;
};

int main()
{
    std::vector<Item> c;
    c.push_back({b0 | b1, 123});
    c.push_back({b0 | b2, 123});
    c.push_back({b2, 123});

    for (auto& item : c) {
        if (matches_bits_and_info(b2, 123)(item)) {
            std::cout << item.field << ' ' << item.info << std::endl;
        }
    }

}

预期结果:

5 123
4 123

为了使效率低于多索引容器解决方案(或手动解决方案),数据集必须大量

答案 1 :(得分:1)

选项2非常好,实际上您的解决方案可以正常运行。对于选项1,无论你的Predicate类有多聪明,都无法使它工作,解释如下:非唯一的散列索引(意思是根据某些等价标准打包)元素,在您的情况下,fieldinfo数据成员具有相同的值。让我们暂时忘记info假装所有元素都有相同的值,并专注于field:六个等价类(元素簇)是

0,1,2,3,4,5,6,7

,它们只是field的三个比特0,1和2可以组合使用的不同值(这些聚类不一定按上面显示的顺序出现)。现在,如果您尝试执行equal_range检索所有b2元素,请设置您想要的操作,以返回下面粗体字母中显示的那些群集中的元素:

0,1, 2 3 ,4,5, 6 7

一般来说没有排列在一起,所以没有办法 equal_range可以返回几个迭代器,范围超过这些和只有这些元素。

答案 2 :(得分:1)

可以考虑替代1,考虑到field成员的可能值实际上并不是那么多(8),即逐个拾取 具有给定位集的相应field值,请参阅以下示例中的for_each_value

<强> Live Coliru Demo

#include <algorithm>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/composite_key.hpp>

enum Bit
{
  b0 = 1,
  b1 = 2,
  b2 = 4
};

struct Item
{
  int field;
  int info;
};

struct MyTag;

using boost::multi_index_container;
using namespace boost::multi_index;

typedef composite_key<
  Item,   
  member<Item, int, &Item::field>,
  member<Item, int, &Item::info> > CompositeKey;
typedef hashed_non_unique< tag<MyTag>, CompositeKey> Index1;
typedef boost::multi_index_container<Item, indexed_by<Index1> > Container;

template<typename F>
F for_each_value(const Container& c,Bit b,int info,F f)
{
  for(auto field:{0,1,2,3,4,5,6,7}){
    if(field&b){
      auto r=c.equal_range(std::make_tuple(field,info));
      std::for_each(r.first,r.second,f);
    }
  }
  return f;
}

#include <iostream>

int main()
{
  Item item1 = { int( b0 | b1), 123};
  Item item2 = { int( b1 | b2), 123};
  Item item3 = { int( b2), 123};

  Container c;
  c.insert( item1);
  c.insert( item2);
  c.insert( item3);

  for_each_value(c,b2,123,[](const Item& i){
    std::cout << i.field << ' ';
  });
}

<强>输出

4 6