Boost Multi Index: index based on list content - c++

I'm new to Boost Multi Index container, and was wondering if it could solve in a more effecient way my problem, simplified thus:
struct A {
int id;
}
struct B {
int id;
std::list<A> products;
}
Each A having a strictly unique ID, I want to be able with a single multi index container, be able to look for B.
By B's id, and by A's Id.
At the moment i'm working with nice std::map, and map linking A ids to B ids.
So to say. It works fine enough. But, I'm having other parameters for such a look up and it's getting really nasty :)
EDIT:
As per comment request I'll elaborate somewhat:
I've Satellites, which in turn have many Transponders and many Sources.
I want to be able to find a Satellite for a given Transponder id or Source id( which are indeed unique )
Sadly I don't have hand on Satellite struct, means, I can't alter it.
Briefly it looks like that :
struct Satellite {
int norad_id;
std::list<Transponder> transponders;
std::list<Source> sources;
... some more data
}
What I want to do is simply search a whatever of Satellites, and find a Satellite having a specific transponder-, or source-, or norad id.
At the moment, I'm using 3 nice maps
std::map<int /*norad*/ ,Satellite> satellites;
std::map<int /*transponder id*/, int/* norad */> transponder_to_satellite;
std::map<int /* source_id */, int /* norad */ > source_to_satellite;
From the example #sehe provided, I see it would be somewhat easier if I were to spawn a relationnal struct. I guess I'll give it a try ... :)

In the absense of exact use cases, here' some suggestions to model the indices based on what you showed¹
struct Product {
int id;
};
struct Category {
int id;
};
struct ProductCategoryRelation {
int productId;
int categoryId;
};
namespace bmi = boost::multi_index;
using RelationTable = bmi::multi_index_container<
ProductCategoryRelation,
bmi::indexed_by<
bmi::ordered_unique<
bmi::tag<struct by_product>,
bmi::member<ProductCategoryRelation, int, &ProductCategoryRelation::productId>
>,
bmi::ordered_unique<
bmi::tag<struct by_category>,
bmi::member<ProductCategoryRelation, int, &ProductCategoryRelation::categoryId>
>
>
>;
You could also get quite smart with a composite key, which is versatile in ordered_* indexes:
using RelationTable = bmi::multi_index_container<
ProductCategoryRelation,
bmi::indexed_by<
bmi::ordered_unique<
bmi::tag<struct by_product>,
bmi::composite_key<ProductCategoryRelation,
bmi::member<ProductCategoryRelation, int, &ProductCategoryRelation::categoryId>,
bmi::member<ProductCategoryRelation, int, &ProductCategoryRelation::productId>
>
>
>
>;
Here's a small demo:
Live On Coliru
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <boost/multi_index/global_fun.hpp>
#include <list>
struct Product {
int id;
};
struct Category {
int id;
};
struct ProductCategoryRelation {
int productId;
int categoryId;
};
namespace bmi = boost::multi_index;
using RelationTable = bmi::multi_index_container<
ProductCategoryRelation,
bmi::indexed_by<
bmi::ordered_unique<
bmi::tag<struct compound>,
bmi::composite_key<ProductCategoryRelation,
bmi::member<ProductCategoryRelation, int, &ProductCategoryRelation::categoryId>,
bmi::member<ProductCategoryRelation, int, &ProductCategoryRelation::productId>
>
>
>
>;
#include <iostream>
#include <boost/range/iterator_range.hpp>
int main() {
RelationTable table {
ProductCategoryRelation { 1, 7 },
ProductCategoryRelation { 2, 7 },
ProductCategoryRelation { 3, 7 },
ProductCategoryRelation { 4, 6 },
ProductCategoryRelation { 5, 6 },
ProductCategoryRelation { 6, 6 },
ProductCategoryRelation { 7, 5 },
ProductCategoryRelation { 8, 5 },
ProductCategoryRelation { 9, 5 },
};
// find all products in category 6:
for (auto& rel : boost::make_iterator_range(table.get<compound>().equal_range(6)))
std::cout << "Product " << rel.productId << " is in category " << rel.categoryId << "\n";
}
Prints:
Product 4 is in category 6
Product 5 is in category 6
Product 6 is in category 6
¹ I crystal-balled the class names into something "realistic"

Related

boost bimap with associated value

Is there a way to construct a boost::bimap (or multi-index container) that has two keys, plus a value they both point to? In addition, you can query one key to get the other?
I can construct a boost multi-index container that has two keys for some element, but cannot figure out how to get the value of a key given the value of the other key?
I am trying to do something like:
struct s {};
int main()
{
typedef std::map<int, std::shared_ptr<s>> left_map_type;
typedef std::map<std::string, std::shared_ptr<s>> right_map_type;
typedef boost::bimap<left_map_type, right_map_type> bi_map_type;
typedef bi_map_type::value_type value_type;
bi_map_type bim;
std::shared_ptr<s> sp = std::make_shared<s>();
std::shared_ptr<s> sp2 = std::make_shared<s>();
left_map_type lm { {1, sp}, {2, sp2} };
right_map_type rm { {"foo", sp}, {"bar", sp2} };
bim.insert(lm, rm);
/*
For instance, given the key "foo", search bim to obtain BOTH the shared pointer sp as well as the alternate key '1'.
*/
}
I would use a multi-index container over records like:
struct Data { }; // your "s"
struct Record {
int id;
std::string name;
Data data; // std::shared_ptr<Data>?
};
Now you could make a container that adds unique indexes by id and name:
using Table = boost::multi_index_container<Record,
bmi::indexed_by<
bmi::ordered_unique< bmi::tag<struct by_id>, bmi::member<Record, int, &Record::id>>,
bmi::ordered_unique< bmi::tag<struct by_name>, bmi::member<Record, std::string, &Record::name>>
> >;
Formatting more verbosely:
using Table = boost::multi_index_container<
Record,
bmi::indexed_by<
bmi::ordered_unique<
bmi::tag<struct by_id>,
bmi::member<Record, int, &Record::id>>,
bmi::ordered_unique<
bmi::tag<struct by_name>,
bmi::member<Record, std::string, &Record::name>>>>;
See below for a less verbose way;
Now you can make your table and access it using any of the indices:
Table table;
auto& left = table.get<by_id>(); // or get<0>
auto& right = table.get<by_name>(); // or get<1>
Whatever interface you use, any changes will reflect in all other indexes, and uniqueness constraints are guaranteed. E.g.
table.emplace(1, "one", Data{"Sleepy", {1, 2, 3}});
table.emplace(2, "two", Data{"Grumpy", {2, 4, 6}});
table.emplace(3, "three", Data{"Sneezy", {3, 6, 9}});
Just printing them (using libfmt for demo):
// Simple enumeration:
fmt::print("Just the table:\n - {}\n", fmt::join(table, "\n - "));
fmt::print("By id:\n - {}\n", fmt::join(left, "\n - "));
fmt::print("By name:\n - {}\n", fmt::join(right, "\n - "));
Prints
Just the table:
- Record{1, one, Data{Sleepy, {1, 2, 3}}}
- Record{2, two, Data{Grumpy, {2, 4, 6}}}
- Record{3, three, Data{Sneezy, {3, 6, 9}}}
By id:
- Record{1, one, Data{Sleepy, {1, 2, 3}}}
- Record{2, two, Data{Grumpy, {2, 4, 6}}}
- Record{3, three, Data{Sneezy, {3, 6, 9}}}
By name:
- Record{1, one, Data{Sleepy, {1, 2, 3}}}
- Record{3, three, Data{Sneezy, {3, 6, 9}}}
- Record{2, two, Data{Grumpy, {2, 4, 6}}}
This exemplifies that the default index is the first index declared (the "left view" here, or as I'd prefer to call it: by_id).
Searching is just like you'd expect from a standard container:
auto id2 = left.find(2);
auto nameTwo = right.find("two");
if (id2 != left.end())
fmt::print("id2: {}\n", *id2);
if (nameTwo != right.end())
fmt::print("nameTwo: {}\n", *nameTwo);
For non-unique indexes, equal_range is useful. There's lower_bound and upper_bound etc.
Live Demo
Live On Compiler Explorer
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index_container.hpp>
#include <memory>
struct Data {
std::string extra;
std::vector<int> ints;
};
struct Record {
int id;
std::string name;
Data data; // std::shared_ptr<Data>?
};
namespace bmi = boost::multi_index;
#define Index(name) \
bmi::ordered_unique< \
bmi::tag<struct by_##name>, \
bmi::member<Record, decltype(Record::name), &Record::name>>
using Table = boost::multi_index_container<Record,
bmi::indexed_by<
Index(id),
Index(name)
> >;
#include <fmt/ranges.h>
template <>
struct fmt::formatter<Data, char> : fmt::formatter<std::string, char> {
auto format(Data const& data, auto& ctx) {
return fmt::format_to(ctx.out(), "Data{{{}, {}}}", data.extra,
data.ints);
}
};
template <>
struct fmt::formatter<Record, char> : fmt::formatter<std::string, char> {
auto format(Record const& rec, auto& ctx) {
return fmt::format_to(ctx.out(), "Record{{{}, {}, {}}}", rec.id,
rec.name, rec.data);
}
};
int main()
{
Table table;
auto& left = table.get<by_id>(); // or get<0>
auto& right = table.get<by_name>(); // or get<1>
table.emplace(1, "one", Data{"Sleepy", {1, 2, 3}});
table.emplace(2, "two", Data{"Grumpy", {2, 4, 6}});
table.emplace(3, "three", Data{"Sneezy", {3, 6, 9}});
// Simple enumeration:
fmt::print("Just the table:\n - {}\n", fmt::join(table, "\n - "));
fmt::print("By id:\n - {}\n", fmt::join(left, "\n - "));
fmt::print("By name:\n - {}\n", fmt::join(right, "\n - "));
// find:
auto id2 = left.find(2);
auto nameTwo = right.find("two");
if (id2 != left.end())
fmt::print("id2: {}\n", *id2);
if (nameTwo != right.end())
fmt::print("nameTwo: {}\n", *nameTwo);
}
Printing the outbove shown above.
Advanced Topics/Usages
A few things to keep in mind:
if you really needed to share ownership of the data, you can, of course use shared_ptr<Data> instead
you can also construct multi-index containers over references/pointers, so I'd advice against shared_ptr unless you know it's actually required
more flexible key extraction mechanisms exist (e.g. you could have a non-unique ordered index on Record::data::ints::length())
you can have composite keys, which support partial querying on ordered indexes. This enable "database like" queries, see e.g. Boost multi-index container vs a multi-level mapping container based on std::unordered_map (map of maps) or Using boost multi index like relational DB
like with the standard containers, the key elements are const. In multi-index containers this implies that all accessors return const references. Refer to the document for modify, replace functions.
there's a project function to convert iterators between indexes, should you ever require this
Bonus: Less Verbose?
Or, with a clever macro to reduce repetition¹
#define Index(name) \
bmi::ordered_unique< \
bmi::tag<struct by_##name>, \
bmi::member<Record, decltype(Record::name), &Record::name>>
using Table = boost::multi_index_container<Record,
bmi::indexed_by<
Index(id),
Index(name)
> >;
¹ I was momentarily too lazy to make that template meta functions instead of the macro

How can I Iterate over Vertices & Edges in an Order Provided by a (Bundled) Property, in the BGL?

Say I have some boost graph
#include <boost/graph/adjacency_list.hpp>
struct Vertex {
double property_1;
int property_2;
};
using Graph_t = boost::adjacency_list<boost::listS,
boost::listS,
boost::undirectedS,
Vertex,
boost::no_property>;
Graph_t g(5);
and now want to iterate over the vertices in different orders, say:
by its id
in a random order
descending by property_2
ascending by property_1
descending/ascending by more bundled properties in a generic way.
How do I do this in the most efficient way?
As of now, I created std::vectors with the properties, and vectors containing indices, and sorted them by the properties. But if you have many properties that creates a ton of structure that could be avoided.
I also looked at boost::multi_index maps, as in this cplusplus.com question, but that doesn't seem slim to me either.
How can I do this? Happy about any hint!
Boost.MultiIndex can be plugged in in a rather convoluted, undocumented fashion:
Live Coliru Demo
#include <boost/graph/adjacency_list.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/multi_index/ordered_index.hpp>
struct mic_tag:
/* it is assumed first index is random-access */
virtual public boost::graph_detail::random_access_container_tag,
virtual public boost::graph_detail::back_insertion_sequence_tag{};
namespace boost{
template<typename... Args>
mic_tag container_category(boost::multi_index_container<Args...>&){return {};}
}
template<typename GraphType,typename KeyExtractor>
struct vertex_adapted
{
using result_type=typename KeyExtractor::result_type;
decltype(auto) operator()(void* p)const
{
return key(
static_cast<typename GraphType::stored_vertex*>(p)->m_property);
}
KeyExtractor key;
};
struct vertex_t
{
double property_1;
int property_2;
};
struct graph_t;
struct graph_t_vertex_list;
namespace boost{
template<typename Value>
struct container_gen<graph_t_vertex_list,Value>
{
using type=boost::multi_index_container<
Value,
boost::multi_index::indexed_by<
boost::multi_index::random_access<>,
boost::multi_index::ordered_non_unique<
vertex_adapted<
graph_t,
boost::multi_index::member<vertex_t,double,&vertex_t::property_1>
>
>,
boost::multi_index::ordered_non_unique<
vertex_adapted<
graph_t,
boost::multi_index::member<vertex_t,int,&vertex_t::property_2>
>,
std::greater<int>
>
>
>;
};
}
struct graph_t:
boost::adjacency_list<
boost::listS,
graph_t_vertex_list,
boost::undirectedS,
vertex_t
>{};
/* testing */
#include <iostream>
std::ostream& operator<<(std::ostream& os,const vertex_t& v)
{
os<<"{"<<v.property_1<<","<<v.property_2<<"}";
return os;
}
int main()
{
graph_t g;
add_vertex(vertex_t{0.0,0},g);
add_vertex(vertex_t{0.1,1},g);
add_vertex(vertex_t{0.2,2},g);
for(void* p:g.m_vertices.get<1>()){
std::cout<<static_cast<graph_t::stored_vertex*>(p)->m_property;
}
std::cout<<"\n";
for(void* p:g.m_vertices.get<2>()){
std::cout<<static_cast<graph_t::stored_vertex*>(p)->m_property;
}
std::cout<<"\n";
}
Output
{0,0}{0.1,1}{0.2,2}
{0.2,2}{0.1,1}{0,0}
Update Apr 14: I've refactored things a bit so that the resulting user code is much more straightforward:
struct vertex_t
{
double property_1;
int property_2;
};
using graph_t= boost::adjacency_list<
boost::listS,
mic_listS<
boost::multi_index::ordered_non_unique<
boost::multi_index::member<vertex_t,double,&vertex_t::property_1>
>,
boost::multi_index::ordered_non_unique<
boost::multi_index::member<vertex_t,int,&vertex_t::property_2>,
std::greater<int>
>
>,
boost::undirectedS,
vertex_t
>;
Complete code follows:
Live Coliru Demo
#include <boost/graph/adjacency_list.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/random_access_index.hpp>
template<typename KeyExtractor>
struct mic_list_key_extractor
{
using result_type=typename KeyExtractor::result_type;
template<typename StoredVertex>
decltype(auto) operator()(StoredVertex& v)const{return key(v.m_property);}
KeyExtractor key;
};
template<typename IndexSpecifier,typename=void>
struct mic_list_index_specifier{using type=IndexSpecifier;};
template<
template<typename...> class IndexSpecifier,
typename Arg1,typename Arg2,typename... Args
>
struct mic_list_index_specifier<
IndexSpecifier<Arg1,Arg2,Args...>,
std::void_t<typename IndexSpecifier<Arg1,Arg2,Args...>::key_from_value_type>>
{
static constexpr bool has_tag=boost::multi_index::detail::is_tag<Arg1>::value;
using arg1=std::conditional_t<has_tag,Arg1,mic_list_key_extractor<Arg1>>;
using arg2=std::conditional_t<has_tag,mic_list_key_extractor<Arg2>,Arg2>;
using type=IndexSpecifier<arg1,arg2,Args...>;
};
template<typename IndexSpecifier>
using mic_list_index_specifier_t=
typename mic_list_index_specifier<IndexSpecifier>::type;
template<typename Value,typename... IndexSpecifiers>
struct mic_list:boost::multi_index_container<
Value,
boost::multi_index::indexed_by<
boost::multi_index::random_access<>,
mic_list_index_specifier_t<IndexSpecifiers>...
>
>
{};
template<typename... IndexSpecifiers>
struct mic_listS;
struct mic_list_tag:
virtual public boost::graph_detail::random_access_container_tag,
virtual public boost::graph_detail::back_insertion_sequence_tag{};
namespace boost{
template<typename... Args>
mic_list_tag container_category(const mic_list<Args...>&){return {};}
template<typename Value,typename... IndexSpecifiers>
struct container_gen<mic_listS<IndexSpecifiers...>,Value>
{
using type=mic_list<Value,IndexSpecifiers...>;
};
namespace detail
{
template<typename... IndexSpecifiers>
struct is_random_access<mic_listS<IndexSpecifiers...>>
{
static constexpr bool value=true;
using type=boost::mpl::true_;
};
}
}
/* testing */
#include <boost/multi_index/ordered_index.hpp>
#include <iostream>
struct vertex_t
{
double property_1;
int property_2;
};
using graph_t= boost::adjacency_list<
boost::listS,
mic_listS<
boost::multi_index::ordered_non_unique<
boost::multi_index::member<vertex_t,double,&vertex_t::property_1>
>,
boost::multi_index::ordered_non_unique<
boost::multi_index::member<vertex_t,int,&vertex_t::property_2>,
std::greater<int>
>
>,
boost::undirectedS,
vertex_t
>;
std::ostream& operator<<(std::ostream& os,const vertex_t& v)
{
os<<"{"<<v.property_1<<","<<v.property_2<<"}";
return os;
}
int main()
{
graph_t g;
add_vertex(vertex_t{0.0,0},g);
add_vertex(vertex_t{0.1,1},g);
add_vertex(vertex_t{0.2,2},g);
for(const auto& v:g.m_vertices.get<1>()){
std::cout<<v.m_property;
}
std::cout<<"\n";
for(const auto& v:g.m_vertices.get<2>()){
std::cout<<v.m_property;
}
std::cout<<"\n";
}
Output
{0,0}{0.1,1}{0.2,2}
{0.2,2}{0.1,1}{0,0}
That's (obviously) not a feature of the library.
You can however use ranges or range adaptors, like you would in any other situation:
Live On Coliru
#include <boost/graph/adjacency_list.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/algorithm_ext.hpp>
#include <fmt/ranges.h>
#include <fmt/ostream.h>
#include <random>
struct Vertex {
double property_1;
int property_2;
};
static inline std::ostream& operator<<(std::ostream& os, Vertex const& v) {
return os << "V(" << v.property_1 << ", " << v.property_2 << ")";
}
using Graph_t =
boost::adjacency_list<boost::listS, boost::listS, boost::undirectedS,
Vertex, boost::no_property>;
int main() {
using boost::make_iterator_range;
using namespace boost::adaptors;
Graph_t g(5);
int i = 0;
for (auto& v : make_iterator_range(vertices(g))) {
++i;
g[v] = {i / -.3, i * 11};
}
auto get_bundle = [&g](auto v) -> auto& { return g[v]; };
fmt::print("Natural order: {}\n",
make_iterator_range(vertices(g)));
fmt::print("Natural order: {}\n",
make_iterator_range(vertices(g) | transformed(get_bundle)));
fmt::print(
"Reverse natural order: {}\n",
make_iterator_range(vertices(g) | transformed(get_bundle) | reversed));
auto make_view = [=](auto range) {
return std::vector<std::reference_wrapper<Vertex>>(
begin(range), end(range));
};
auto view =
make_view(make_iterator_range(vertices(g) | transformed(get_bundle)));
boost::reverse(view);
fmt::print("view: {}\n", view);
boost::reverse(view);
fmt::print("reversed: {}\n", view);
auto less_by = [](auto member) {
return [=, prj = std::mem_fn(member)](auto const& a, auto const& b) {
return prj(a) < prj(b);
};
};
boost::sort(view, less_by(&Vertex::property_1));
fmt::print("less_by property_1: {}\n", view);
boost::sort(view, less_by(&Vertex::property_2));
fmt::print("less_by property_2: {}\n", view);
{
static std::random_device rd;
static std::mt19937 randgen{rd()};
std::shuffle(view.begin(), view.end(), randgen);
fmt::print("random order: {}\n", view);
}
// just a loop is also fine, of course
i = 0;
for (Vertex& bundle : view) {
bundle.property_2 = i++;
}
fmt::print("modified: {}\n", view);
}
Prints
Natural order: {0x1467eb0, 0x1467f10, 0x1467f70, 0x1467fd0, 0x1468030}
Natural order: {V(-3.33333, 11), V(-6.66667, 22), V(-10, 33), V(-13.3333, 44), V(-16.6667, 55)}
Reverse natural order: {V(-16.6667, 55), V(-13.3333, 44), V(-10, 33), V(-6.66667, 22), V(-3.33333, 11)}
view: {V(-16.6667, 55), V(-13.3333, 44), V(-10, 33), V(-6.66667, 22), V(-3.33333, 11)}
reversed: {V(-3.33333, 11), V(-6.66667, 22), V(-10, 33), V(-13.3333, 44), V(-16.6667, 55)}
less_by property_1: {V(-16.6667, 55), V(-13.3333, 44), V(-10, 33), V(-6.66667, 22), V(-3.33333, 11)}
less_by property_2: {V(-3.33333, 11), V(-6.66667, 22), V(-10, 33), V(-13.3333, 44), V(-16.6667, 55)}
random order: {V(-13.3333, 44), V(-3.33333, 11), V(-10, 33), V(-6.66667, 22), V(-16.6667, 55)}
modified: {V(-13.3333, 0), V(-3.33333, 1), V(-10, 2), V(-6.66667, 3), V(-16.6667, 4)}
More, From Here
std::ranges could give you most of these but in my experience has a few more limitations. However, it will be generally safer (because Boost Range V2 is quite old).
to have "living indexes" (like a database) make your vertex container selector select a Multi Index Container. See e.g. the advice here https://marc.info/?l=boost&m=118835654637830
to model your own graph datastructure, see e.g. here for inspiration
What is needed to use BGL algorithms on existing data structures ( edges and vertices as vector<Object *>)?
What is required for a custom BGL graph to work with topological sort?
UPDATE Code Generation With Boost PFR
In response to the comments, you could use Boost PFR to generate a array with comparators simple types statically:
template <typename T, typename Op = std::less<> >
constexpr static inline auto make_field_comparers(Op op = {}) {
namespace pfr = boost::pfr;
auto constexpr N = pfr::tuple_size<T>::value;
using A = std::array<std::function<bool(T const&, T const&)>, N>;
auto lift = [op](auto prj) {
return [=](T const& a, T const& b) { return op(prj(a), prj(b)); };
};
return [lift]<size_t... I>(std::index_sequence<I...>){
return A{lift([](T const& v) { return pfr::get<I>(v); })...};
}
(std::make_index_sequence<N>{});
}
Which you could use like Live On Compiler Explorer
std::vector orderings {
std::pair { "asc", make_field_comparers<Vertex>() },
std::pair { "desc", make_field_comparers<Vertex>(std::greater<>{}) },
};
for (auto const& [dir, fields] : orderings) {
for (size_t field = 0; field < fields.size(); ++field) {
boost::sort(view, fields[field]);
fmt::print("by field #{} {}: {}\n", field, dir, view);
}
}
Printing
by field #0 asc: {V(-16.6667, 55), V(-13.3333, 44), V(-10, 33), V(-6.66667, 22), V(-3.33333, 11)}
by field #1 asc: {V(-3.33333, 11), V(-6.66667, 22), V(-10, 33), V(-13.3333, 44), V(-16.6667, 55)}
by field #0 desc: {V(-3.33333, 11), V(-6.66667, 22), V(-10, 33), V(-13.3333, 44), V(-16.6667, 55)}
by field #1 desc: {V(-16.6667, 55), V(-13.3333, 44), V(-10, 33), V(-6.66667, 22), V(-3.33333, 11)}

How to perform equal_range on one key and lower_bound on the second key of a composite keyed boost multi-index container?

Let say I have a class to hold the sensor measurements, and I created a boost multi-index container with composite key of time and id of each measurement:
namespace {
struct ValueUpdateMsg {
double value;
uint64_t time;
int id;
};
struct time_id {
};
struct id_time {
};
using value_set_t = bmi::multi_index_container<
ValueUpdateMsg,
bmi::indexed_by<
bmi::ordered_unique<
bmi::tag<struct id_time>,
bmi::composite_key<ValueUpdateMsg,
bmi::member<ValueUpdateMsg, decltype(ValueUpdateMsg::id), &ValueUpdateMsg::id>,
bmi::member<ValueUpdateMsg, uint64_t, &ValueUpdateMsg::time>
>
>,
bmi::ordered_unique<
bmi::tag<struct time_id>,
bmi::composite_key<ValueUpdateMsg,
bmi::member<ValueUpdateMsg, uint64_t, &ValueUpdateMsg::time>,
bmi::member<ValueUpdateMsg, decltype(ValueUpdateMsg::id), &ValueUpdateMsg::id>
>
>
>
>;
}
value_set_t container;
container.insert(ValueUpdateMsg{1, 0, 1.0});
container.insert(ValueUpdateMsg{1, 1, 2.0});
container.insert(ValueUpdateMsg{3, 0, 3.0});
container.insert(ValueUpdateMsg{3, 2, 4.0});
container.insert(ValueUpdateMsg{5, 0, 5.0});
container.insert(ValueUpdateMsg{5, 1, 6.0});
I would like to find the node whose id=2 and the update time less that to equal to 4. How can I do that in the boost-multi-index container?
I can perform the following:
auto id2_range = boost::make_iterator_range(container.get<id_time>().equal_range(std::make_tuple(2)));
To get a range of values whose id == 2
and perform a linear( or binary) search to find the node whose time matches the query. Is there is a better way of doing it in boost multi-index?
auto id2_range = boost::make_iterator_range(
container.get<id_time>().lower_bound(2),
container.get<id_time>().upper_bound(std::make_tuple(2,4))
);

Multiple indexes query in Boost Multi-Index

I'm writing a software that stores GenericOrder (containing a quantity, a price, a way and a timestamp) as shared_ptr.
I've read Boost documentation and succeed to define a MultiIndexOrderContainer using three indexes: way, timestamp and price.
But I don't find a way to iterate on specific orders using multiple indexes at the same time.
#include <memory>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/identity.hpp>
#include <boost/multi_index/member.hpp>
using namespace ::boost;
using namespace ::boost::multi_index;
enum class Way
{
UNDEFINED,
BUY,
SELL
};
template <typename QuantityType, typename PriceType>
struct GenericOrder
{
explicit GenericOrder(const Way way, const QuantityType& quantity, const PriceType& price, const long long& timestamp)
: way_(way), quantity_(quantity), price_(price), timestamp_(timestamp)
{
}
~GenericOrder() = default;
GenericOrder(const GenericOrder&) = delete;
GenericOrder& operator=(const GenericOrder&) = delete;
Way way_;
QuantityType quantity_;
PriceType price_;
long long timestamp_ = -1;
};
// Aliases
using QuantityType = int;
using PriceType = int;
using OrderType = GenericOrder<QuantityType, PriceType>;
using PointerType = std::shared_ptr<OrderType>;
struct way {};
struct timestamp {};
struct price {};
using MultiIndexOrderContainer = multi_index_container<PointerType,
indexed_by<
ordered_non_unique<tag<way>, member<OrderType, decltype(OrderType::way_), &OrderType::way_ >>,
ordered_non_unique<tag<timestamp>, member<OrderType, decltype(OrderType::timestamp_), &OrderType::timestamp_ >>,
ordered_non_unique<tag<price>, member<OrderType, decltype(OrderType::price_), &OrderType::price_>>
>
>;
int main()
{
MultiIndexOrderContainer c;
// Inserting some orders
c.insert(std::make_shared<OrderType>(Way::BUY, 10, 15, 0));
c.insert(std::make_shared<OrderType>(Way::BUY, 10, 14, 1));
c.insert(std::make_shared<OrderType>(Way::BUY, 10, 13, 2));
c.insert(std::make_shared<OrderType>(Way::SELL, 10, 16, 3));
c.insert(std::make_shared<OrderType>(Way::SELL, 10, 17, 4));
c.insert(std::make_shared<OrderType>(Way::SELL, 10, 18, 5));
return 0;
}
I would like to iterate on:
On buying orders with a specific price sorted by timestamp
Cheapest order price from selling orders
Costliest order price from buying orders
How can I achieve that ?
Boost.MultiIndex does not provide any special mechanism for mixing the orders induced by different indices: with the structure you propose you're basically down to querying for the first parameter and then doing a linear scan on the returned range.
On the other hand, if your queries are always of the form (attr1, attr2, attr3) you can speed them up by using composite keys. In your particular case, you can have the three queries you're after with a composite key on (way_,price_,timestamp_)
Live On Coliru
#include <memory>
#include <iostream>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <boost/multi_index/identity.hpp>
#include <boost/multi_index/member.hpp>
using namespace ::boost;
using namespace ::boost::multi_index;
enum class Way
{
UNDEFINED,
BUY,
SELL
};
template <typename QuantityType, typename PriceType>
struct GenericOrder
{
explicit GenericOrder(const Way way, const QuantityType& quantity, const PriceType& price, const long long& timestamp)
: way_(way), quantity_(quantity), price_(price), timestamp_(timestamp)
{
}
~GenericOrder() = default;
GenericOrder(const GenericOrder&) = delete;
GenericOrder& operator=(const GenericOrder&) = delete;
Way way_;
QuantityType quantity_;
PriceType price_;
long long timestamp_ = -1;
};
template <typename QuantityType, typename PriceType>
std::ostream& operator<<(std::ostream& os,const GenericOrder<QuantityType,PriceType>& o)
{
switch(o.way_){
case Way::UNDEFINED: os<<"UNDEFINED, ";break;
case Way::BUY: os<<"BUY, ";break;
case Way::SELL: os<<"SELL, ";break;
}
return os<<o.price_<<", "<<o.timestamp_<<"\n";
}
// Aliases
using QuantityType = int;
using PriceType = int;
using OrderType = GenericOrder<QuantityType, PriceType>;
using PointerType = std::shared_ptr<OrderType>;
struct way {};
struct timestamp {};
struct price {};
using MultiIndexOrderContainer = multi_index_container<PointerType,
indexed_by<
ordered_non_unique<
composite_key<
OrderType,
member<OrderType, decltype(OrderType::way_), &OrderType::way_ >,
member<OrderType, decltype(OrderType::price_), &OrderType::price_>,
member<OrderType, decltype(OrderType::timestamp_), &OrderType::timestamp_ >
>
>
>
>;
int main()
{
MultiIndexOrderContainer c;
// Inserting some orders
c.insert(std::make_shared<OrderType>(Way::BUY, 10, 15, 0));
c.insert(std::make_shared<OrderType>(Way::BUY, 10, 14, 1));
c.insert(std::make_shared<OrderType>(Way::BUY, 10, 13, 2));
c.insert(std::make_shared<OrderType>(Way::BUY, 10, 15, 1));
c.insert(std::make_shared<OrderType>(Way::SELL, 10, 16, 3));
c.insert(std::make_shared<OrderType>(Way::SELL, 10, 17, 4));
c.insert(std::make_shared<OrderType>(Way::SELL, 10, 18, 5));
std::cout<<"Buying orders for 15, sorted by timestamp\n";
auto p=c.equal_range(std::make_tuple(Way::BUY,15));
while(p.first!=p.second)std::cout<<**p.first++;
std::cout<<"Cheapest selling order\n";
std::cout<<**c.lower_bound(Way::SELL);
std::cout<<"Costliest buying order\n";
std::cout<<**--c.upper_bound(Way::BUY);
return 0;
}

C++ Minecraft2D Block Type?

I was working on a Minecraft2D kind of game in Java and I decided to create the same game in C++ to enhance my C++ abilities. But I have a problem. I had a BlockType enum in Java which contained that BlockType's image location and hardness (how long it takes to mine it). I figured out that in C++ enums are different than the ones in Java. How can I implement this in C++?
BlockType.java:
public enum BlockType {
STONE("res/blocks/stone.png",3),
COAL("res/blocks/coal.png", 2),
AIR("res/blocks/air.png",0),
GRASS("res/blocks/grass.png",1),
DIRT("res/blocks/dirt.png",1),
DIAMOND("res/blocks/diamond.png",5),
REDSTONE("res/blocks/redstone.png",3),
COBBLE("res/blocks/cobble.png",3),
BRICK("res/blocks/brick.png",4),
IRON("res/blocks/iron.png",4),
GOLD("res/blocks/gold.png",5);
public final String location;
public final int hardness;
BlockType(String location, int hardness){
this.location = location;
this.hardness = hardness;
}
}
I'll go with something similar to SingerOfTheFall answer:
enum blocks
{
STONE,
COAL,
GOLD
};
struct BlockType {
BlockType(std::string loc, int h): location(loc), hardness(h) {}
std::string location;
int hardness;
};
BlockType blockTypes[] = {
BlockType("res/blocks/stone.png", 3), // STONE
BlockType("res/blocks/coal.png", 2), // COAL
BlockType("res/blocks/gold.png", 5) // GOLD
};
// use:
cout << "Location: " << blockTypes[STONE].location << endl;
std::map is a good container, but it uses binary search every time you need to get the value. Indices will be from 0 to n so you can use array instead.
A possibility would be use a std::map, keyed by an enum value and with a value of std::pair<sd::string, int>:
#include <string>
#include <map>
#include <utility>
enum BlockType
{
STONE,
COAL,
GOLD
};
std::map<BlockType, std::pair<std::string, int>> BlockTypes;
BlockTypes[STONE] = std::make_pair(std::string("res/blocks/stone.png"), 3);
BlockTypes[COAL] = std::make_pair(std::string("res/blocks/coal.png"), 2);
BlockTypes[GOLD] = std::make_pair(std::string("res/blocks/gold.png"), 5);
C++ enums work another way indeed.
enum eMyEnum
{
ONE = 15,
TWO = 22
};
is about all you can get from them, basically they just allow you to create 'names' for INT values.
For your case, I would make an enum for the block names:
enum blocks
{
STONE,
SAND,
<...>
};
and then make a map:
< blocks, pair< string, int > >
^ ^ ^ ^
| | | |
| | | hardness
| | path to picture
| |
| the block's attributes: the picture path and hardness
|
the block type from the enum (e.g. SAND)
Or just make a structure to hold three values:
struct block
{
string type;//or int, or your enum type, depending on how do you want to store it.
string picture;
int hardness;
}
I would combine both the answers here and make a mapping of enum to block struct
struct Block
{
block(path, str) : strength(str), path(path) {}
int str;
std::string path;
};
enum BlockType
{
STONE,
COAL,
ETC
}
std::map<BlockType, Block> blocks;
blocks[STONE] = Block("c:/block/bla.png", 1);
blocks[STONE].str; // 1
blocks[STONE].path; // "c:/block/bla.png"
Why use and std::map whan an array will do? (Which can be initialized at compile time)
using namespace std;
struct BlockType {
enum {
STONE = 0,
COAL,
LAST
};
BlockType(string location, int hardness) : location(location), hardness(hardness) {}
const string location;
const int hardness;
static const BlockType Blocks[LAST];
};
const BlockType BlockType::Blocks[] = {
BlockType("res/blocks/stone.png", 3),
BlockType("res/blocks/coal.png", 2)
};
int main() {
cout << BlockType::Blocks[BlockType::STONE].location << `\n`;
return 0;
}