Tryer
Tryer

Reputation: 4050

MultiIndex containers -- offering vector and set access

I have an application where, first, an std::vector<int> object is generated. Then, some operations need to be performed on this object viewed as an std::set<int> where the order does not matter and repetitions don't count.

At present, I explicitly construct an object of type std::set<int> from the std::vector<int> object. An example is presented below:

#include <cstdio>
#include <set>
#include <vector>

void printset(std::set<int>& Set) {
    printf("Printing Set Elements: ");
    for (std::set<int>::iterator siter = Set.begin(); siter != Set.end(); ++siter) {
        int val = *siter;
        printf("%d ", val);
    }
    printf("\n");
}

void printvec(std::vector<int>& Vec) {
    printf("Printing Vec Elements: ");
    for (size_t i = 0, szi = Vec.size(); i < szi; ++i) {
        int val = Vec[i];
        printf("%d ", val);
    }
    printf("\n");
}

int main()
{
    std::vector<int> VecInt{ 6, 6, 5, 5, 4, 4 };
    std::set<int> SetInt(VecInt.begin(), VecInt.end());
    printvec(VecInt);
    printset(SetInt);
}

I am trying to see if I can use Boost.MultiIndex for this purpose. One introduction to Boost.MultiIndex states:

Boost.MultiIndex can be used if elements need to be accessed in different ways and would normally need to be stored in multiple containers. Instead of having to store elements in both a vector and a set and then synchronizing the containers continuously, you can define a container with Boost.MultiIndex that provides a vector interface and a set interface.

and this is precisely what I am doing (using multiple containers and then creating one from the other constantly) while I would like to create a (multi-index) container once and then provide a vector interface and a set interface.

On looking through various examples, for e.g., here and here, it is not clear how those examples can be modified to the code example above.

Ideally, I would like to do the following in the code example above:

MultiIndexContainer vec_set_container;

vec_set_container.push_back(6);//or anything equivalent for the MultiIndexContainer
vec_set_container.push_back(6);
vec_set_container.push_back(5);
vec_set_container.push_back(5);
vec_set_container.push_back(4);
vec_set_container.push_back(4);

printvec(vec_set_container.Some_Function_That_Exposes_Vector_Interface());
printset(vec_set_container.Some_Function_That_Exposes_Set_Interface());

How can this be accomplished using Boost.MultiIndex ?

Upvotes: 2

Views: 820

Answers (1)

sehe
sehe

Reputation: 392903

Random access index would match the "vector" interface.

An ordered unique index would match the "set" interface.

However, if you have a unique index, this will prevent insertion of duplicates. So, you would get:

Live On Compiler Explorer

#include <boost/multi_index/identity.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/multi_index_container.hpp>
#include <fmt/ranges.h>

namespace bmi = boost::multi_index;

using Table = bmi::multi_index_container< //
    int,
    bmi::indexed_by<
        bmi::random_access<bmi::tag<struct asVector>>,
        bmi::ordered_unique<bmi::tag<struct asSet>, bmi::identity<int>>>>;

int main()
{
    Table data{ 6, 6, 5, 5, 4, 4 };

    fmt::print("As vec {}\nAs set {}\n", //
               data.get<asVector>(),    //
               data.get<asSet>());
}

Printing

As vec {6, 5, 4}
As set {4, 5, 6}

Now, I think the "best" you could do with this is to make the order index non-unique (so, mimicking a std::multiset instead of std::set): Live On Compiler Explorer

bmi::ordered_non_unique<bmi::tag<struct asSet>, bmi::identity<int>>

Printing

As vec [6, 6, 5, 5, 4, 4]
As set {4, 4, 5, 5, 6, 6}

If you want to traverse unique elements, using a range adaptor would be minimally costly:

  1. Using Boost Live

    fmt::print("As vec {}\nAs set {}\n", //
               data.get<asVector>(),     //
               data.get<asSet>() | boost::adaptors::uniqued);
    
  2. Using RangeV3 Live

    fmt::print("As vec {}\nAs set {}\n", //
               data.get<asVector>(),     //
               data.get<asSet>() | ranges::views::unique);
    
  3. Using Standard Ranges; I couldn't make this work but it should really be something like std::ranges::unique(data.get<asSet>())

All printing

As vec {6, 6, 5, 5, 4, 4}
As set {4, 5, 6}

Other Ideas

If you don't require random access, then sequenced index might be preferrable for you. And note that this interface comes with the handy unique() and sort() methods (just like std::list).

UPDATE To The Comments

Here's a rolled-in-one response to the comments:

Live On Compiler Explorer

#include <boost/multi_index/identity.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index_container.hpp>
#include <fmt/ranges.h>
#include <random>

namespace bmi = boost::multi_index;

template <typename T>
using ListSet = bmi::multi_index_container< //
    T,
    bmi::indexed_by<
        bmi::sequenced<bmi::tag<struct byInsertion>>,                   //
        bmi::ordered_unique<bmi::tag<struct Ordered>, bmi::identity<T>> //
        >>;

int main()
{
    ListSet<int> data;

    std::mt19937 prng{99}; // "random" seed
    for (std::uniform_int_distribution d(1, 10); data.size() < 5;) {
        int el = d(prng);
        if (auto [it, unique] = data.push_back(el); not unique) {
            fmt::print("Element {} duplicates index #{}\n", el,
                    std::distance(begin(data), it));
        }
    }

    fmt::print("By insertion {}\nOrdered {}\n", data, data.get<Ordered>());
}

Prints

Element 9 duplicates index #3
Element 8 duplicates index #1
By insertion [7, 8, 5, 9, 1]
Ordered {1, 5, 7, 8, 9}

Upvotes: 4

Related Questions