Ben
Ben

Reputation: 33

Is it possible to have boost multi_index container index one element with 2 key values?

I want to have my boost multi_index container index one element with multiple key values on the same index. Is that possible?

struct Student {
    int Id;
    std::unordred_set<std::string> Clubs;
};

Suppose this student belongs to Technology and Movie clubs, and I have unique hashed index on Id and non_unique hashed index on clubs (I want to know what students are in each club by looking at club name).

I want to be able to find the same student by searching for either "Technology" or "Movie" using boost multi_index. Is it possible with boost or do I need to roll my own data store?

Upvotes: 1

Views: 524

Answers (3)

Elaborating on Sehe's answer, this is a realization of the relation table idea using Boost.Flyweight to hold the individual entities:

Live Coliru Demo

#include <boost/flyweight.hpp>
#include <boost/flyweight/key_value.hpp>
#include <boost/flyweight/no_tracking.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/key.hpp>
#include <boost/range/iterator_range.hpp>
#include <iostream>
#include <string>
#include <utility>

using namespace boost::flyweights;
using namespace boost::multi_index;

struct student_impl
{
  student_impl(
    int id,const std::string& name="",const std::string& surname=""):
    id{id},name{name},surname{surname}{}
    
  int         id;
  std::string name;
  std::string surname; 
};

using student=flyweight<
  key_value<int,student_impl,key<&student_impl::id>>,
  no_tracking
>;
using club=flyweight<std::string>;

static student::initializer sinit;
static club::initializer    cinit;

using club_student_row=std::pair<club,student>;
using club_student_table=multi_index_container<
  club_student_row,
  indexed_by<
    hashed_non_unique<key<&club_student_row::first>>,
    hashed_non_unique<key<&club_student_row::second>>
  >
>;

club_student_table club_student;

template<typename Student,typename... Strs>
void add_to_clubs(const Student& s,const Strs&... strs)
{
  (club_student.emplace(strs,s),...);
}

void print_students_in_club(const club& c)
{
  std::cout<<c<<": ";
  for(const auto& [_,s]:
      boost::make_iterator_range(club_student.equal_range(c))){
    std::cout<<s.get().name<<" "<<s.get().surname<<"; ";
  }
  std::cout<<"\n";
}

void print_clubs_for_student(const student& s)
{
  std::cout<<s.get().name<<" "<<s.get().surname<<": ";
  for(const auto& [c,_]:
      boost::make_iterator_range(club_student.get<1>().equal_range(s))){
    std::cout<<c<<"; ";
  }
  std::cout<<"\n";
}

int main()
{
  add_to_clubs(student_impl(1,"Ben"),"Technology");
  add_to_clubs(student_impl(2,"Caleth"),"Technology","Movie");
  add_to_clubs(student_impl(3,"Sehe"),"Technology","Latin");
  add_to_clubs(student_impl(4,"Joaquin","Lopez"),"Movie","Latin");
  
  print_students_in_club(club("Technology"));
  print_clubs_for_student(student(4));
}

Output

Technology: Sehe ; Caleth ; Ben ; 
Joaquin Lopez: Latin; Movie;

Upvotes: 1

sehe
sehe

Reputation: 393064

Yes you can have keys with multiple values (like your set). You can also include the same element in the same container multiple times: Wherever you write multi_index_container<T, ...> you can write that as multi_index_container<T*, ...> or multi_index_container<reference_wrapper<T>, ...>.

However this does not provide the additional lookup requirements.

You would usually model your table as a relation table, so you can have some records "shaped" like:

Student Id Club Key
1 "Technology"
1 "Movie"

You can model the Student type so it doesn't need to contain the club memberships directly. Instead you can query that from the same relation table.

Upvotes: 1

Caleth
Caleth

Reputation: 62704

You can't do it with a multi_index containing your Student type, but you can use (something based on it) with a different type.

using Id_t = int;
using Club_t = std::string;
using StudentClubs = boost::bimap<boost::bimap::multiset_of<Id_t>, boost::bimap::multiset_of<Club_t>>;

StudentClubs student_clubs = /* some data */;

You can then lookup all the students in the movie club

for (auto [club, student] : boost::iterator_range(student_clubs.right.equal_range("movie"))) {
    std::cout << student;
}

Or all the clubs that student 3 is in

for (auto [student, club] : boost::iterator_range(student_clubs.left.equal_range(3))) {
    std::cout << club;
}

Upvotes: 1

Related Questions