1

I've created a boost::multi_index_container (containerSet) over a container class and indexed the containerSet by std::string and std::set<int>. Is it possible to get the container, which store a specific int inside their set? Furthermore is it possible to get all container, which store at least one value between int1 and int2 within their set?

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/format.hpp>
#include <boost/lambda/core.hpp>
#include <iostream>

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

class Container {
public:
    std::set<int> set;
    std::string name;

    Container(std::string name, std::set<int> set);
    ~Container();

    friend std::ostream& operator<<(std::ostream& os,const Container& c) {
        os << c.name << ", [ ";
        for (int i : c.set) {
            os << i << "  ";
        }
        os << "]";
        return os;
    }
};

Container::Container(std::string name = "noName", std::set<int> set = {}) : name{name} ,set{set} {}
Container::~Container() {}

struct setTag{};
struct nameTag{};

typedef multi_index_container<Container, indexed_by<
        ordered_unique<tag<nameTag>, BOOST_MULTI_INDEX_MEMBER(Comp, std::string, name)>,
        ordered_unique<tag<setTag>, BOOST_MULTI_INDEX_MEMBER(Comp, std::set<int>, set)>
>> ContainerSet;

//don't see how I could get the compare structs to work, because
//a) can't fullfill the strict weak odering requirements and
//b) because of the setTag ordering, not all set's get called
struct compSetRange {
        bool operator()(int x,const std::set<int> &c) const {}
        bool operator()(const std::set<int> &c, int x) const {}
};
struct compSetFind {
    bool operator()(int x,const std::set<int> &c) const {}
    bool operator()(const std::set<int> &c, int x) const {}
};

int main() {
    Container c1{"c1", {5, 6, 7, 18, 61, 77}};
    Container c2{"c2", {2, 4, 5, 21, 36, 88, 99}};
    Container c3{"c3", {2, 3, 9, 10, 65, 75, 91}};
    ContainerSet cs;
    cs.insert(c1);
    cs.insert(c2);
    cs.insert(c3);

    std::cout << "print by name (ordered)" << std::endl;
    for (auto e : cs.get<nameTag>()) {
        std::cout << e << std::endl;
    }
    std::cout << std::endl;

    std::cout << "print by set (ordered)" << std::endl;
    for (auto e : cs.get<setTag>()) {
        std::cout << e << std::endl;
    }
    std::cout << std::endl;

    typedef ContainerSet::index<setTag>::type compBySetIndex;
    //find(std::set) works but isn't useful in my case
    compBySetIndex::iterator it1 = cs.get<setTag>().find(std::set<int>{2, 4, 5, 21, 36, 88, 99});
   //TODO: find all comps with int 5 -> c1 and c2
//  compBySetIndex::iterator it1 = cs.get<setTag>().find(200, compSetFind());
    if (it1 !=cs.get<setTag>().end()) {
        std::cout << *it1 << std::endl;
    }

    //TODO: find all container with values between 70 and 80 -> c1 and c3
//  compBySetIndex::iterator it1_low = cs.get<setTag>().lower_bound(70, compSetRange());
//  compBySetIndex::iterator it1_upp = cs.get<setTag>().upper_bound(80, compSetRange());

    //.range() also not applicable

    return 0;
}

With the sets:
c3 = {2, 3, 9, 10, 65, 75, 91}
c2 = {2, 4, 5, 21, 36, 88, 99}
c1 = {5, 6, 7, 18, 61, 77}
I want to be able to call ...find(5); and get at least c2, maybe even c1 on the next invocation. This might be doable with the right Compare function, but I can't think of a way to make the operator() functions compatible.
Furthermore after ...lower_bounds(70) and ...upper_bounds(80) I should get c3 and c1. Because of the ordering of the std::set's, this requirement seems unachievable with boost.

Am I missing something? Thanks in advance!


I know I could do a linear search over all containers and their sets to achieve my goal, but that would negate the performance advantage of the multi_index_container. If boost is the wrong tool for this job I will have to resort to an individual class containerSet.

user3608078
  • 316
  • 2
  • 9
  • You are saying that your index is of type std::set, but you want to call find with type int, which isn't the index. This likely indicates that you actually want an int for your index. However, then you have the problem that you actually have multiple results for each of your Container items inside the multi_index_container for that index, which doesn't make sense nor work with multi_index container. As you said, you can find these values using iterations, but not with an index, as the index doesn't map through to what you want. – Andrew May 18 '17 at 02:43
  • 2
    In short, you are trying to use multi_index_container with data that isn't indexable the way you want it to be. – Andrew May 18 '17 at 02:45

1 Answers1

2

Andrew diagnosed the issue quite accutely.

To help you along, let me advertise a widely under-used library in Boost: Boost Interval Container.

I hope this demo can shed some light on how useful Boost ICL can be.

Live On Coliru

#include <boost/icl/separate_interval_set.hpp>
#include <boost/icl/interval_map.hpp>

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/member.hpp>

#include <iostream>
#include <numeric>
#include <vector>

using Set = std::set<int>;

struct io_wrap { Set const& ref; };
static std::ostream& operator<<(std::ostream& os,const io_wrap& s) { os << "[ "; for (auto i : s.ref) os << i << " "; return os << ']'; }

namespace icl = boost::icl;
namespace bmi = boost::multi_index;

struct Record {
    std::string name;
    Set set;

    Record(std::string name = "noName", Set set = {}) : name{name}, set{set} {}

    friend std::ostream& operator<<(std::ostream& os,const Record& c) { return os << c.name << ", " << io_wrap{c.set}; }
};

using Map      = icl::interval_map<int, std::set<Record const*> >;
using Interval = Map::interval_type;
using Table    = bmi::multi_index_container<
         std::reference_wrapper<Record>,
         bmi::indexed_by<
             bmi::ordered_unique<
                bmi::tag<struct byName>,
                bmi::member<Record, std::string, &Record::name>
             >
         >
     >;

auto interval_set(Set const& is) { return std::accumulate(is.begin(), is.end(), icl::interval_set<int> { } ); }
auto envelope(Record const& r)   { return hull(interval_set(r.set)); }

void insert(Map& into, Set const& is, std::set<Record const*> const& rs = {}) {
    for (auto i : interval_set(is))
        into += Map::value_type { i, rs };
}

int main() {

    ////////////////////////////////
    // Prepare data
    std::vector<Record> backing_storage {
        {"c3", {2, 3, 9, 10, 65, 75, 91}},
        {"c1", {5, 6, 7, 18, 61, 77}},
        {"c2", {2, 4, 5, 21, 36, 88, 99}},
        // outliers
        {"c4", {0}},
        {"c5", {200}},
    };

    Table const byname(backing_storage.begin(), backing_storage.end());
    Map cs;
    for (auto& r : backing_storage) 
        insert(cs, r.set, { &r });

    ////////////////////////////////
    // Usage demos
    std::cout << "print by name (ordered)\n";
    for (auto const& e : byname) { std::cout << " - " << e << " - envelope: " << envelope(e) << "\n"; }
    std::cout << "\n";

    auto perform_match = [&cs](auto key) {
        Map::codomain_type matches;
        Map::codomain_combine combine;

        for (auto p : cs & key)
            combine(matches, p.second);

        std::cout << "matching " << key << ":\n";
        for (auto const* r : matches)
            std::cout << " - " << *r << "\n";
        std::cout << "\n";
    };

    for (auto key : { Set{2}, {99}, {2,99}, {2,99,5} }) {
        perform_match(interval_set(key));
    }

    perform_match(Interval::right_open(70, 81));
}

Prints:

print by name (ordered)
 - c1, [ 5 6 7 18 61 77 ] - envelope: [5,77]
 - c2, [ 2 4 5 21 36 88 99 ] - envelope: [2,99]
 - c3, [ 2 3 9 10 65 75 91 ] - envelope: [2,91]
 - c4, [ 0 ] - envelope: [0,0]
 - c5, [ 200 ] - envelope: [200,200]

matching {[2,2]}:
 - c3, [ 2 3 9 10 65 75 91 ]
 - c2, [ 2 4 5 21 36 88 99 ]

matching {[99,99]}:
 - c2, [ 2 4 5 21 36 88 99 ]

matching {[2,2][99,99]}:
 - c3, [ 2 3 9 10 65 75 91 ]
 - c2, [ 2 4 5 21 36 88 99 ]

matching {[2,2][5,5][99,99]}:
 - c3, [ 2 3 9 10 65 75 91 ]
 - c1, [ 5 6 7 18 61 77 ]
 - c2, [ 2 4 5 21 36 88 99 ]

matching [70,81):
 - c3, [ 2 3 9 10 65 75 91 ]
 - c1, [ 5 6 7 18 61 77 ]
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Interesting solution! Do you think it can be difficult to make the indexes unique? – Elia Sep 01 '17 at 09:50
  • @Elia Which indexes? There's the by-name index and the interval-domain index. Both are unique in all senses I can think of (there's only 1 set of records for any particular domain interval). – sehe Sep 01 '17 at 11:21
  • In your example `Record` `c3` and `c2` have the same `Set` of 2. What if I want to search on all `Record`s the only that have the `int` key 2 ?? In you example the result is `c3` and `c2`. Maybe I misunderstood? – Elia Sep 01 '17 at 13:34
  • @Elia Oh, so not as much "unique" but "exact". I think you want this: http://coliru.stacked-crooked.com/a/0df9aa7037965afc – sehe Sep 01 '17 at 13:45
  • thank you but not exactly. What I mean is: If `c2` have on it's `set` the value 2 then `c3` cannot have on it's `set`. – Elia Sep 01 '17 at 13:49
  • @Elia Ah. That's exclusive and it makes it more complicated.I think I'd make a bimap (in effect storing only 1:1 (name, int) relations instead of (name, set) pairs): http://coliru.stacked-crooked.com/a/61be860914261e3c – sehe Sep 01 '17 at 17:56
  • By the way, this would have been better as its own question on [SO] – sehe Sep 01 '17 at 17:57