Find All Sorted in Boost

2019-06-08 14:08发布

问题:

Using the example provided on the Boost website: http://www.boost.org/doc/libs/1_55_0/libs/multi_index/example/basic.cpp I've learned how to get elements by an index using iterators. However, I would like to get all matched by one index and sorted by another.

For instance, in the given example, I would like to get all employees whose name is "John", but have the results returned to me ordered by age. As of right now, when I get by name, I am returned an itertor to the first occurance of "John," and so on, but there is no order to the way they were returned other than the order in which the records were inserted.

Please help!

Relevent code:

/* Boost.MultiIndex basic example.
 *
 * Copyright 2003-2008 Joaquin M Lopez Munoz.
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or copy at
 * http://www.boost.org/LICENSE_1_0.txt)
 *
 * See http://www.boost.org/libs/multi_index for library home page.
 */

#if !defined(NDEBUG)
#define BOOST_MULTI_INDEX_ENABLE_INVARIANT_CHECKING
#define BOOST_MULTI_INDEX_ENABLE_SAFE_MODE
#endif

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <string>

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

/* an employee record holds its ID, name and age */

struct employee
{
  int         id;
  std::string name;
  int         age;

  employee(int id_,std::string name_,int age_):id(id_),name(name_),age(age_){}

  friend std::ostream& operator<<(std::ostream& os,const employee& e)
  {
    os<<e.id<<" "<<e.name<<" "<<e.age<<std::endl;
    return os;
  }
};

/* tags for accessing the corresponding indices of employee_set */

struct id{};
struct name{};
struct age{};

/* see Compiler specifics: Use of member_offset for info on
 * BOOST_MULTI_INDEX_MEMBER
 */

/* Define a multi_index_container of employees with following indices:
 *   - a unique index sorted by employee::int,
 *   - a non-unique index sorted by employee::name,
 *   - a non-unique index sorted by employee::age.
 */

typedef multi_index_container<
  employee,
  indexed_by<
    ordered_unique<
      tag<id>,  BOOST_MULTI_INDEX_MEMBER(employee,int,id)>,
    ordered_non_unique<
      tag<name>,BOOST_MULTI_INDEX_MEMBER(employee,std::string,name)>,
    ordered_non_unique<
      tag<age>, BOOST_MULTI_INDEX_MEMBER(employee,int,age)> >
> employee_set;

template<typename Tag,typename MultiIndexContainer>
void print_out_by(
 const MultiIndexContainer& s,
 Tag* =0 /* fixes a MSVC++ 6.0 bug with implicit template function parms */
)
{
  /* obtain a reference to the index tagged by Tag */

  const typename boost::multi_index::index<MultiIndexContainer,Tag>::type& i=
    get<Tag>(s);

  typedef typename MultiIndexContainer::value_type value_type;

  /* dump the elements of the index to cout */

  std::copy(i.begin(),i.end(),std::ostream_iterator<value_type>(std::cout));
}


int main()
{
  employee_set es;

  es.insert(employee(0,"Joe",31));
  es.insert(employee(1,"Robert",27));
  es.insert(employee(2,"John",40));

  /* next insertion will fail, as there is an employee with
   * the same ID
   */

  es.insert(employee(2,"Aristotle",2387));

  es.insert(employee(3,"Albert",20));
  es.insert(employee(4,"John",57));

  /* list the employees sorted by ID, name and age */

  std::cout<<"by ID"<<std::endl;
  print_out_by<id>(es);
  std::cout<<std::endl;

  std::cout<<"by name"<<std::endl;
  print_out_by<name>(es);
  std::cout<<std::endl;

  std::cout<<"by age"<<std::endl;
  print_out_by<age>(es);
  std::cout<<std::endl;

  return 0;
}

回答1:

You can do it in one of two ways:

1: Replace the second index with an index based on a composite key of (name,age):

typedef multi_index_container<
  employee,
  indexed_by<
    ordered_unique<
      tag<id>,  member<employee,int,&employee::id>>,
    ordered_non_unique<
      tag<name>,
      composite_key<
        employee,
        member<employee,std::string,&employee::name>,
        member<employee,int,&employee::age>
      >
    >,
    ordered_non_unique<
      tag<age>, member<employee,int,&employee::age>>
  >
> employee_set;

int main()
{
  employee_set es={
    {0,"John",31},{1,"Robert",27},{2,"John",57},
    {5,"John",2387},{3,"Albert",20},{4,"John",40}};

  for(auto p=es.get<name>().equal_range("John");p.first!=p.second;++p.first){
    std::cout<<*(p.first);
  }
}

2: Sort the results:

template<typename Iterator>
std::vector<std::reference_wrapper<const employee>>
sort_by_age(std::pair<Iterator,Iterator> p)
{
  std::vector<std::reference_wrapper<const employee>> v(p.first,p.second);
  std::sort(v.begin(),v.end(),
    [](const employee& e1,const employee& e2){return e1.age<e2.age;});
  return v;
}

int main()
{
  employee_set es={
    {0,"John",31},{1,"Robert",27},{2,"John",57},
    {5,"John",2387},{3,"Albert",20},{4,"John",40}};

  for(auto e:sort_by_age(es.get<name>().equal_range("John"))){
    std::cout<<e;
  }
}

Which is better? #1 gives the desired result without further postprocessing, but insertions into the index are (slightly) slower as the comparison criterion is more expensive than plain comparison by names. #2 has the postprocessing penalty, but can be used with sorting criteria other than age (with #1, which one is the second key gets fixed at definition time).



标签: c++ boost