(Как) Могу ли я приблизить "динамический" индекс (ключевой экстрактор) для Boost MultiIndex? - PullRequest
1 голос
/ 23 апреля 2010

У меня есть контейнер MultiIndex boost::shared_ptrs для членов класса Host. Эти члены содержат частные массивы bool infections[NUM_SEROTYPES], раскрывающие статусы заражения хостов по каждому из 1, ..., NUM_SEROTYPES серотипов. Я хочу иметь возможность в любой момент определить в симуляции количество людей, зараженных данным серотипом, но я не уверен, как:

  • В идеале, Boost MultiIndex позволил бы мне сортировать, например, по Host::isInfected( int s ), где s - интересующий серотип. Насколько я понимаю, экстракторам ключей MultiIndex не разрешается принимать аргументы.
  • Альтернативой может быть определение индекса для каждого серотипа, но я не вижу, как написать контейнер MultiIndex typedef ... таким расширяемым способом. Я буду менять количество серотипов между симуляциями. (Считают ли опытные программисты, что это возможно? Я попробую, если это так).
  • Существует 2 ^ (NUM_SEROTYPES) ​​возможных статуса заражения. Для небольшого числа серотипов я мог бы использовать один индекс, основанный на этом числе (или двоичную строку), и придумать некоторое сопоставление этого ключа с фактическим состоянием заражения. Подсчет по-прежнему медлителен.
  • Я мог бы вести отдельную структуру, подсчитывая общее количество зараженных каждым серотипом. Синхронизация - это немного больно, но память в порядке. Я предпочел бы вариант сглаживания, так как я хотел бы выполнить дальнейшую сортировку по другим атрибутам хоста (например, после подсчета числа зараженных серотипом s, подсчитайте число инфицированных, которые также находятся в определенном домашнем хозяйстве и имеют определенный возраст) ,

Заранее спасибо.


Обновление

Я собираюсь путешествовать и не смогу проверить какие-либо ответы до среды, 5 мая. Это означает, что тот, кто ответит наиболее популярно, согласно голосам других, выиграет награду (при условии, что соблюден некоторый минимальный порог) ) - Я не вернусь вовремя, чтобы выбрать то, что я считаю лучшим ответом. Пожалуйста, предположите, что для этой награды NUM_SEROTYPES ~ 8-10, поэтому я не хочу переходить к третьему варианту или чему-либо, требующему некоторого ужасного комбинаторного перечисления. Я действительно надеюсь найти способ сортировки MultiIndex по статусу заражения хоста по заданному серотипу.

Кроме того, если это глупый вопрос, я хотел бы знать, почему. Спасибо.


Обновление 2, 6 мая

Эти ответы полезны и помогают мне понять мои возможности, так как я продолжаю оптимизировать и настраивать программу. На данный момент я не могу присудить вознаграждение или решение, но если бы я мог, он бы предоставил наиболее полное и информативное решение.

Моя текущая стратегия будет поддерживать int allInfecteds[ ALL_AGES ][ NUM_SEROTYPES ]. Поддержка массива будет встроена в определения событий (например, для смерти, восстановления, старения и заражения). Получение общего числа зараженных членов домохозяйства в определенном возрасте будет осуществляться путем индивидуального опроса хостов после сортировки MultiIndex по интересующему домохозяйству. (Домохозяйства чрезвычайно малы по отношению к общему количеству хостов.) Поскольку мои запросы становятся более сложными, я мог бы заменить двумерный массив на мультикарты и использовать count_if. Если мы в конечном итоге симулируем с относительно небольшим количеством серотипов, я мог бы попытаться использовать приведенный ниже пример метапрограммирования или использовать явный индекс.

Искренне благодарю всех комментаторов за помощь в изучении различных вариантов и их стоимости. Если я попытаюсь выполнить переиндексацию или метапрограммирование, предложенное ниже, я опубликую результаты здесь.

Ответы [ 5 ]

2 голосов
/ 04 мая 2010

Решение 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 индексов приводит к потере памяти и времени вставки. Более простое решение - просто сохранить один индекс произвольного доступа и отсортировать его соответствующим образом (с помощью специального компаратора, подходящего для конкретного интересующего серотипа в каждый момент) перед запросом.

1 голос
/ 02 мая 2010

Если все состояния серотипа полностью независимы, и вы не можете позволить себе создать, скажем, вектор для каждого серотипа и соответственно нажать / стереть shared_ptrs, вы ничего не можете сделать, чтобы ускориться, чем просто просмотреть их все, количество произвольных серотипов. Теперь это может быть оптимизировано в некотором смысле, например, двоичная строка, но, по сути, это то же самое.

Я думал о каком-то минимальном значении серотипа. Если вы собираетесь запрашивать множество различных серотипов в быстром порядке, вы можете сохранить в объекте максимальный серотип и минимальный имеющийся у него серотип, эффективно исключая его из всех оставшихся поисков одновременно.

1 голос
/ 01 мая 2010

Чтобы реализовать вариант 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_container (Serotype::sup всего), с вероятной эффективностью O (I * lg (n)) для каждого индекса в течение всего времени моделирования (где I - количество событий заражения, а n - количество хостов). Если есть один или два общих запроса, вы можете использовать составные ключи , с одним из компонентов экстракторов ключей серотипа.

Вариант 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) - это стоимость пересекающихся множеств и S и T - размеры наборов (O(f(I)+f(N)) получается из построения всех наборов; я играю его немного незаметно с учетом времени, так как я можно предположить, что f (x) гомоморфно , что, скорее всего, не так). В лучшем случае, f(x)=x и g(X,Y)=X+Y, в зависимости от вашей установленной реализации. В качестве альтернативы, сбросьте по одному из наборов (например, найдите всех хозяев определенного возраста, затем подсчитайте число зараженных; count_if - это конкретная складка, которую вы можете использовать, где count_if(start, end, pred) эквивалентно foldl(start, end, [](int count, const boost::shared_ptr<Host>& h){return count + pred(h); })). В последнем случае используйте самый ограничительный индекс, чтобы максимально сократить число подгрупп, которые необходимо сканировать. Это O (f (I) + S) в целом.

Часть трудностей при разработке эффективного решения заключается в том, что вы действительно можете использовать только один независимый индекс за раз. Внутри групп из индекса другие атрибуты, по которым вы можете индексировать, расположены в основном случайным образом. Если два индекса не являются независимыми (те, которые географически близки, с большей вероятностью могут иметь одинаковые инфекции), вы можете использовать оба одновременно, хотя это не дает вам большого успеха: вы можете получить большую часть суб -популяция в начале запроса, но вам все равно придется изучить остальную группу из первичного индекса.

Если вы не состоите в браке с C ++, вы можете переключиться на C # и обрабатывать запросы с помощью LINQ.

Что касается переключения на базу данных, как предполагает mathmike, вы, вероятно, захотите использовать только объектно-ориентированную базу данных в памяти. Кристофер Браун перечисляет некоторые, в том числе FastDB и OOFile , хотя я не могу сказать, насколько они подходят для вашего проекта.

1 голос
/ 02 мая 2010

Я считаю ваш вопрос очень реалистичным.

Поскольку число серотипов кажется большим (~ 100), я не думаю, что вариант индексации по серотипу может быть допустимым вариантом.Если количество серотипов уменьшено '~ 10), ответ outis - правильный путь для создания индексов для серотипов.

Если бы мне пришлось реализовать вашу проблему, и если одна из главных целей -Чтобы «иметь возможность в любое время определить количество людей, зараженных данным серотипом», я должен вести счетчик для каждого возможного серотипа и увеличивать / уменьшать каждый раз, когда добавляю элемент в контейнер с этим серотипом.Конечно, вы можете инкапсулировать это в классе, который поддерживает счетчики и мультииндекс.Конечно, это навязчиво, но важно предоставить то, что вам нужно.

Это просто и эффективно.

Если вам нужно перебрать все хосты, удовлетворяющие заданному запросу "после подсчета числа инфицированных серотипом s подсчитайте число инфицированных, которые также находятся в определенном домохозяйстве и имеют определенный возраст ", вы могли бы сделать следующее

  • , чтобы получить индекс, связанный с возрастом иполучить диапазон людей с данным возрастом (equal_range) или
  • получить индекс относительно домашнего хозяйства и получить диапазон людей с данным домохозяйством (equal_range)

иэтот индекс подсчитывает все хосты, удовлетворяющие другим условиям, используя 'count_if'.

Также может быть полезна функция итератора проекта.

Даже если у вас нет серотипа, более простой запрос«подсчитать число инфицированных, которые также находятся в определенном домохозяйстве и имеют определенный возраст» для двух информационных элементов, которыеДля того чтобы получить индекс, вам нужно использовать count_if для одного из индексов.Слияния двух диапазонов не существует.

Это просто, имеет хороший компромисс между эффективностью и данными.

Если вам нужно сделать сложный запрос, который должен быть оченьДля эффективности вам нужно будет добавить индекс, который позаботится обо всех условиях.Например, для предыдущего запроса вы можете добавить индекс, отсортированный по домашнему хозяйству и возрасту.Использование этого индекса даст вам непосредственно с равным_распределением соответствующие элементы, но вам еще нужно будет подсчитать их, используя count_if.Это будет более эффективным, поскольку набор элементов для итерации будет короче.

1 голос
/ 29 апреля 2010

Самое простое решение вашей проблемы - просто подсчитать количество зараженных людей, когда вы захотите узнать ответ. Это означает, что вы должны зацикливаться на каждом хосте во время запроса, но это не комбинаторно.

Если это слишком медленно, я бы предложил сохранить мультикарту от серотипа до каждого зараженного хоста. Это имеет те же проблемы с синхронизацией, что и ваш четвертый вариант, но имеет то преимущество, что вы легко получаете доступ ко всем зараженным хостам для вычисления дополнительной статистики.

Если вы действительно выполняете много запросов, основанных на множестве различных атрибутов, и не хотите вручную создавать структуры данных, чтобы сделать каждый запрос эффективным, вы можете рассмотреть возможность использования реляционной базы данных SQL. Они действительно предназначены для таких запросов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...