Повысьте эффективность контейнера multi_index с помощью составного ключа, пользовательского компаратора и поиска по частичному индексу.

У меня есть такой класс (упрощенный пример):

class A {
  public:
    typedef boost::shared_ptr<A> Ptr;
    const std::string& getID() const;
    const std::string& getLabel() const;
    bool getFlag() const;
    float getValue() const;
  private:
  ...
};

Мне нужен контейнер, который индексируется уникальной комбинацией (id, label), а также уникальной комбинацией (label, flag, value). Мне также нужно, чтобы он был отсортирован по второму индексу по метке, за которой следует флаг, за которым следуют уменьшающиеся значения, если флаг истинен, и увеличивающиеся значения, если флаг неверен. Итак, после создания экстракторов ключей я делаю что-то вроде этого:

typedef boost::multi_index::composite_key<
  Ptr, extractor_id, extractor_label
> id_label_key;
typedef boost::multi_index::composite_key<
  Ptr, extractor_label, extractor_flag, extractor_value
> label_flag_value_key;
...
typedef boost::multi_index_container<Ptr,
  boost::multi_index::indexed_by<
    boost::multi_index::ordered_unique<
      boost::multi_index::tag<by_id_label
      id_label_key
    >,
    boost::multi_index::ordered_unique<
      boost::multi_index::tag<by_label_flag_value>,
      label_flag_value_key,
      Compare
    >,
  >
> Items;
typedef Items::index<by_label_flag_value>::type Items_by_label_flag_value;

где Сравнить определяется как:

struct Compare {
  bool operator() (const boost::multi_index::composite_key_result<label_flag_value_key>& k, const boost::tuple<float,bool>& q) const {
    return compare(k.value->getLabel(), k.value->getFlag(), k.value->getValue(),
      q.get<0>(), q.get<1>(), q.get<2>()
  }
  bool operator() (const boost::tuple<float,bool>& q, const boost::multi_index::composite_key_result<label_flag_value_key>& k) const {
    return compare(q.get<0>(), q.get<1>(), q.get<2>(), 
      k.value->getLabel(), k.value->getFlag(), k.value->getValue(),
  }
  bool operator() (const boost::multi_index::composite_key_result<label_flag_value_key>& k1, const boost::multi_index::composite_key_result<label_flag_value_key>& k2) const {
    return compare(k1.value->getLabel(), k1.value->getFlag(), k1.value->getValue(),
      k2.value->getLabel(), k2.value->getFlag(), k2.value->getValue())
  }
  bool compare(const std::string& l1, bool f1, float v1, const std::string& l2, bool f2, float v2) const {
    if (l1 != l2) return l1 < l2;
    if (f1 != f2) return f1;
    return f1 ? (v1 > v2) : (v1 < v2);
  }
};

Теперь я могу выполнять такие запросы:

Items_by_label_flag_value::const_iterator it = items_by_label_flag_value.find(boost::make_tuple("A", true, 0.1));

Однако, если я попытаюсь выполнить частичный запрос, например, получить все элементы с одинаковыми метками, мой код не скомпилируется:

std::pair<Items_by_label_flag_value::const_iterator, Items_by_label_flag_value::const_iterator> range = items_by_label_flag_value.equal_range(boost::make_tuple("A"));

Я знаю, почему он не компилируется: в компараторе я явно использую .get<0>(), .get<1>() и .get<2>(), но кортеж частичного поиска не имеет элементов <1> и <2>. Чего я не знаю, так это как создать правильный компаратор. Если я попытаюсь добавить к нему еще две функции, которые принимают кортежи только из одного элемента, то компилятор жалуется на неоднозначность в вызове operator().

Я также понимаю, что composite_key_result должен быть непрозрачным объектом, и мне не следует использовать его внутренности.

Итак, мой вопрос: как создать нужный индекс и правильный компаратор?


person Vladimir    schedule 07.05.2013    source источник


Ответы (2)


Что касается вашего исходного решения, вам не нужно использовать составные ключи для вашего второго индекса, поскольку ваш экстрактор Compare в основном пытается заменить механизм, автоматически сгенерированный composite_key/composite_key_compare. Следующее было (слегка) проверено на работу:

struct LBFCompare
{
  bool operator() (const A& x, const A& y) const {
    return compare(
      x.getLabel(), x.getFlag(), x.getValue(),
      y.getLabel(), y.getFlag(), y.getValue());
  }
  bool operator() (const std::string& x, const A& y) const{
    return compare(x,y.getLabel());
  }
  bool operator() (const A& x, const std::string& y) const{
    return compare(x.getLabel(),y);
  }
  template<typename T0>
  bool operator() (const boost::tuple<T0>& x, const A& y) const{
    return compare(x.get<0>(),y.getLabel());
  }
  template<typename T0>
  bool operator() (const A& x, const boost::tuple<T0>& y) const{
    return compare(x.getLabel(),y.get<0>());
  }
  template<typename T0,typename T1>
  bool operator() (const boost::tuple<T0,T1>& x, const A& y) const{
    return compare(x.get<0>(),x.get<1>(),y.getLabel(),y.getFlag());
  }
  template<typename T0,typename T1>
  bool operator() (const A& x, const boost::tuple<T0,T1>& y) const{
    return compare(x.getLabel(),x.getFlag(),y.get<0>(),y.get<1>());
  }
  template<typename T0,typename T1,typename T2>
  bool operator() (const boost::tuple<T0,T1,T2>& x, const A& y) const{
    return compare(x.get<0>(),x.get<1>(),x.get<2>(),y.getLabel(),y.getFlag(),y.getValue());
  }
  template<typename T0,typename T1,typename T2>
  bool operator() (const A& x, const boost::tuple<T0,T1,T2>& y) const{
    return compare(x.getLabel(),x.getFlag(),x.getValue(),y.get<0>(),y.get<1>(),y.get<2>());
  }
  bool compare(const std::string& l1, const std::string& l2) const {
    return l1 < l2;
  }
  bool compare(const std::string& l1, bool f1, const std::string& l2, bool f2) const {
    if (l1 != l2) return l1 < l2;
    return f1 < f2;
  }
  bool compare(const std::string& l1, bool f1, float v1, const std::string& l2, bool f2, float v2) const {
    if (l1 != l2) return l1 < l2;
    if (f1 != f2) return f1;
    return f1 ? (v1 > v2) : (v1 < v2);
  }
};

struct by_id_label{};
struct by_label_flag_value{};

typedef boost::multi_index_container<
  Ptr,
  boost::multi_index::indexed_by<
    boost::multi_index::ordered_unique<
      boost::multi_index::tag<by_id_label>,
      id_label_key
    >,
    boost::multi_index::ordered_unique<
      boost::multi_index::tag<by_label_flag_value>,
      boost::multi_index::identity<A>,
      LBFCompare
    >
  >
> Items;
typedef Items::index<by_label_flag_value>::type Items_by_label_flag_value;

int main()
{
  Items c;
  c.insert(Ptr(new A("id","label",true,1.0)));
  Items_by_label_flag_value& i=c.get<by_label_flag_value>();
  i.find("id");
  i.find(boost::make_tuple("id"));
  i.find(boost::make_tuple("id",true));
  i.find(boost::make_tuple("id",true,1.0));
}

Упомянутые вами проблемы неоднозначности связаны с тем, что вы, вероятно, ищете, передавая кортежи с const char*s, а не полностью сформированные std::strings: в этой ситуации есть неявные преобразования и, по-видимому, кортежи 1-, 2- и 3-размера одинаково хорошие кандидаты (имхо проблема реализации с кортежами). Решение состоит в том, чтобы шаблонизировать те LBFCompare::operator(), которые принимают кортежи.

person Joaquín M López Muñoz    schedule 08.05.2013

Очень интересная проблема! Самый простой способ решить эту проблему, о которой я могу думать, это: добавить следующее в свой класс

class A {
  ...
  float getTaggedValue()const{return getFlag()?-getValue():getValue();}
  ...
};

а затем оборудуйте свой второй индекс обычным составным ключом на (label,tag,tagged_value). Выполняя поиск с кортежами (l,t,v), вы не должны забывать иметь vnegated, если тег истинен (приложив немного больше усилий, вы можете заставить getTaggedValue возвращать специальный тип, скажем, pair<bool, double>, чтобы вы непреднамеренно не передали непосредственно в кортеж непроверенное число с плавающей запятой.)

person Joaquín M López Muñoz    schedule 07.05.2013
comment
Спасибо! Да, я тоже так сделал (что также сделало ненужным пользовательский компаратор), и я думаю, что это правильное решение. Другой аналогичный метод состоит в том, чтобы иметь две переменные для хранения значения и заполнения одной значением, а другой - нулем в зависимости от флага; этот метод будет предпочтительнее, когда тип значения не плавающий, а что-то, что относительно дорого копировать (например, какое-то десятичное число), чтобы мы всегда могли вернуть const ref в экстракторе ключа. Однако мне все еще интересно, как может быть решена первоначальная проблема или почему она не может быть решена. - person Vladimir; 08.05.2013