(How)我可以为Boost MultiIndex近似一个“动态”索引(键提取器)吗?

时间:2010-04-22 22:26:52

标签: c++ boost

我有一个boost::shared_ptrs的MultiIndex容器给类Host的成员。这些成员包含私有数组bool infections[NUM_SEROTYPES],显示关于1,...,NUM_SEROTYPES血清型中每一个的Hosts感染状态。我希望能够在模拟中随时确定感染特定血清型的人数,但我不确定如何:

  • 理想情况下,Boost MultiIndex允许我对Host::isInfected( int s )进行排序,其中s是感兴趣的血清型。据我所知,MultiIndex键提取器不允许带参数。
  • 另一种方法是为每种血清型定义一个索引,但我不知道如何以这种可扩展的方式编写MultiIndex容器typedef ... 。我将改变模拟之间血清型的数量。 (经验丰富的程序员是否认为这应该是可能的?如果是这样,我会尝试。)
  • 有2 ^(NUM_SEROTYPES)个可能的感染状态。对于少量血清型,我可以使用基于该数字的单个索引(或二进制字符串),并提供从该密钥到实际感染状态的一些映射。计数仍然很慢。
  • 我可以维护一个单独的结构,计算每种血清型的感染总数。同步有点痛苦,但记忆力很好。我更喜欢一个更光滑的选项,因为我想对其他宿主属性做进一步的分类(例如,在计算感染血清型的数量之后,计算那些也在特定家庭中并且具有特定年龄的受感染者的数量) 。

提前致谢。


更新

我要去旅行,在5月5日星期三之前无法检查任何答案。这意味着根据其他人的投票,无论谁回答最受欢迎,都将获得赏金(假设满足一些最低门槛) ) - 我不会及时回过头来选择我认为最好的答案。为了这个赏金的目的,请假设NUM_SEROTYPES~8-10,所以我不想使用第三个选项或任何需要一些可怕的组合枚举的东西。我真的希望找到一种方法来根据给定血清型对宿主感染状态的MultiIndex进行排序。

另外,如果这是一个愚蠢的问题,我很想知道原因。感谢。


5月6日更新2

这些回复很有帮助,可以帮助我了解我的选择,因为我会继续优化和自定义程序。此时我无法给予赏金或解决方案,但如果可以的话,那将提供最彻底和最有用的解决方案。

我目前的策略是维持int allInfecteds[ ALL_AGES ][ NUM_SEROTYPES ]。阵列的维护将被构建到事件定义中(例如,用于死亡,恢复,老化和被感染)。获取特定年龄的受感染家庭成员总数将通过在感兴趣的家庭对MultiIndex进行排序后单独查询主机来完成。 (相对于主机总数,家庭数量非常少。)随着我的查询变得更加复杂,我可能会用多重映射替换二维数组并使用count_if。如果我们最终用相对较少的血清型进行模拟,我可能会尝试使用下面的元编程示例或使用显式索引。

再次感谢所有评论员帮助我了解各种选项及其成本。如果我尝试重建索引或下面提出的元编程解决方案,我将在此处发布结果。

5 个答案:

答案 0 :(得分:2)

解决方案2(为每种血清型维持一个单独的索引可以通过一点元编程来完成:

#include <boost/mpl/push_back.hpp>
#include <boost/mpl/range_c.hpp>
#include <boost/mpl/fold.hpp>
#include <boost/mpl/for_each.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/shared_ptr.hpp>

using namespace boost::multi_index;

struct host
{
  static const int NUM_SEROTYPES=8;

  bool infections[NUM_SEROTYPES];
};

typedef boost::shared_ptr<host> host_ptr;

template<typename Num>
struct infection_extractor
{
  typedef bool result_type;

  result_type& operator()(const host_ptr& p)const
  {
    return p->infections[Num::value];
  }
};

typedef boost::mpl::fold<
  boost::mpl::range_c<int,0,host::NUM_SEROTYPES>,
  boost::mpl::vector<>,
  boost::mpl::push_back<
    boost::mpl::_1,
    ordered_non_unique<infection_extractor<boost::mpl::_2> >
  >
>::type index_list_type;

typedef multi_index_container<
  host_ptr,
  index_list_type
> multi_t;

template<int Num>
std::size_t infected_with(const multi_t& m)
{
  return m.get<Num>().count(true);
};

typedef std::size_t (*infected_with_type)(const multi_t&);

struct infected_with_table_assigner
{
  infected_with_table_assigner(infected_with_type* table):table(table)
  {
    boost::mpl::for_each<
      boost::mpl::range_c<int,0,host::NUM_SEROTYPES>
    >(*this);
  }

  template<typename Num>
  void operator()(Num)const{table[Num::value]=&infected_with<Num::value>;}

  infected_with_type* table;
};

std::size_t infected_with(const multi_t& m,int n)
{
  static infected_with_type           table[host::NUM_SEROTYPES];
  static infected_with_table_assigner assigner(table);

  return table[n](m);
}

#include <iostream>

int main()
{

  multi_t m;
  host h;
  for(int i=0;i<200;++i){
    for(int j=0;j<host::NUM_SEROTYPES;++j)h.infections[j]=(i&(1<<j))?true:false;
    m.insert(host_ptr(new host(h)));
  }

  for(int n=0;n<host::NUM_SEROTYPES;++n)
  {
    std::cout<<"infected with serotype "<<n<<": "
      <<infected_with(m,n)<<std::endl;
  }
}

但考虑到维持8~10个索引会有内存和插入时间损失。一个更简单的解决方案是在查询之前保持一个random access index并对其进行适当的排序(使用适合于每个时刻感兴趣的特定血清型的自定义比较器)。

答案 1 :(得分:1)

解决问题的最简单方法是在想知道答案时计算受感染的人数。这意味着您必须在查询时循环遍历每个主机,但它不是组合的。

如果这太慢了,那么我建议维护一个从血清型到每个受感染宿主的多重图。这与第四个选项具有相同的同步问题,但它的优点是您可以轻松访问所有受感染的主机以计算更多统计信息。

如果您真的根据许多不同的属性进行了大量查询,并且您不想手动构建数据结构以使每个查询都有效,那么您可能需要考虑使用SQL关系数据库。这些都是专为此类查询而设计的。

答案 2 :(得分:1)

要实现选项2,请为另一个类中的血清型定义枚举类型,然后编写模板化的键提取器。我不确定boost::multi_index_container是否支持重新索引(我怀疑它确实如此),所以这种方法可能从一开始就注定失败。

class ... {
    enum Serotype {
        ...
        sup // 1 greater than the highest serotype
    };
    ...
};

template <class H, typename H::Serotype s>
struct serotype_key_extractor {
    typedef bool result_type;

    result_type operator()(const boost::shared_ptr<Host>& h) const 
        { ! return h->isInfected(s); }
};

当然,这需要您的multi_index_containerSerotype::sup总数)上每种血清型的索引,并且在整个生命周期内每个索引的可能性能为O(I * lg(n))模拟(其中 I 是感染事件的数量, n 是主机的数量)。如果有一个或两个常见查询,您可以使用composite keys,血清型密钥提取器为一个组件。

选项4,在集合std::map<H::Serotype, std::set<boost::shared_ptr<Host> > >的映射中跟踪受感染的主机,可以以查询为代价使感染事件更具性能。插入/删除集合可能是O(lg(n)),因此最好使用散列或列表而不是集合,但这会增加查询的成本。由于您正在运行模拟,因此保持此结构同步并不算太糟糕。在感染(或治愈,如果存在)事件期间更新它。如果你正在使用基于事件的编程,那很容易。

对于使用选项4进行查询,您可以根据其他属性构建其他集,然后查找这些集的交集。整体为O(f(I)+f(n)+g(S,T)),其中 f(x) 是插入 x 元素的成本, g(X,Y) < / em>是相交集的成本, S T 是集合的大小(O(f(I)+f(N))来自建立所有的集合;我在时间上玩的有点宽松,因为我可能假设f(x)是homomorphic,它可能不是。最好是f(x)=xg(X,Y)=X+Y,具体取决于您的设置实施。或者,fold超过其中一个集合(例如查找特定年龄的所有主机,然后计算受感染的数量; count_if是您可以使用的特定折叠,其中count_if(start, end, pred)等同于{ {1}})。在后一种情况下,请使用限制性最强的索引来尽可能减少扫描所需的子群。这总体上是O(f(I)+ S)。

设计高性能解决方案的部分困难在于,您一次只能使用一个independent索引。在索引的组内,您可能索引的其他属性基本上是随机排列的。如果两个索引不是独立的(那些地理上接近的索引更可能有相同的感染),你可能可以同时使用它们,虽然这不会让你获得太多:你可以得到大部分的sub - 查询早期的人口,但你仍然需要从主索引中检查组的其余部分。

如果您没有与C ++结婚,可以切换到C#并使用LINQ处理查询。

至于切换到数据库,正如mathmike建议的那样,你可能只想使用object-oriented内存数据库。 Christopher Brown列出了一些内容,包括FastDBOOFile,但我不能说它们对您的项目有多适合。

答案 3 :(得分:1)

我觉得你的问题很现实。

由于血清型的数量似乎很大(约100),我认为按血清型索引的选择不是一个有效的选择。如果血清型的数量减少了“~10”,那么outis的答案就是继续制作血清型指标的正确方法。

如果我必须实施您的问题,并且如果其中一个主要目标是“能够在模拟中随时确定感染特定血清型的人数”,我会为每种可能的血清型维持一个计数器每当我在容器中添加具有该血清型的元素时增加/减少。您当然可以将其封装在维护计数器和多索引的类上。当然这是侵入性的,但提供你需要的东西是非常重要的。

这很简单有效。

如果您需要迭代满足给定查询的所有主机“在计算感染血清型的数量后,计算同样在某个特定家庭并且具有特定年龄的受感染者的数量”,您可以执行以下操作

  • 获取与年龄相关的指数,并获得具有给定年龄的指数范围 年龄(equal_range)或
  • 获取相对于家庭的指数,并获得给定家庭(equal_range)
  • 的范围

并在此索引上使用'count_if'计算满足其他条件的所有主机。

还有一个项目迭代器功能可能会有所帮助。

即使您没有血清型,对于具有索引的两个信息元素,更简单的查询“计算在特定家庭中并且具有特定年龄的受感染者的数量”,您将需要在一个上使用count_if的索引。在两个范围内没有合并的东西。

这很简单,在效率和数据之间有很好的折衷。

如果您需要进行需要非常高效的复杂查询,则需要添加一个处理所有条件的索引。例如,对于前面的查询,您可以添加按家庭和年龄排序的索引。使用此索引将直接为您提供相关元素的equal_range,但您还需要使用count_if来计算它们。这将更有效,因为要迭代的元素集将更短。

答案 4 :(得分:1)

如果所有血清型状态都是完全独立的,并且你不能为每个血清型创建一个载体并适当地推送/擦除shared_ptrs,那么你没有什么可以做得更快,而不仅仅是循环通过它们来确定任意血清型的计数。现在可以在某些方面进行优化,比如二进制字符串,但从根本上说,它是相同的。

我一直在考虑一种最小的血清型值。如果您要快速查询许多不同的血清型,您可以在对象中存储最大血清型和最小血清型,有效地将其从所有剩余的搜索中一次性排除。