WHERE + ORDER BY using boost::multi_index

95 Views Asked by At

Assume I have the following Order structure:

namespace data
{
    using OrderId = int64_t;
    using TimePoint = std::chrono::steady_clock::time_point;
    
    struct Order
    {
        OrderId id;

        double price;

        TimePoint updateTime;

        double average() const
        {
            return price;
        }
    };
}

How to by a given price p iterate over the orders as if I have a similar orders table in a database and run the following query?

SELECT * FROM orders WHERE average > p ORDER BY updateTime DESC;

average is SQL is Order::average() that is something different than price in real-life project.

I was able to define a multi-index:

#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/multi_index/global_fun.hpp>
#include <boost/multi_index/key.hpp>

struct IdTag {};
struct DateTag {};
struct PriceTag {};

using IdKey = boost::multi_index::ordered_unique<boost::multi_index::tag<IdTag>, boost::multi_index::key<&data::Order::id>>;
using DateKey = boost::multi_index::ordered_non_unique<boost::multi_index::tag<DateTag>, boost::multi_index::key<&data::Order::updateTime>>;
using PriceKey = boost::multi_index::ordered_non_unique<boost::multi_index::tag<PriceTag>, boost::multi_index::key<&data::Order::average>>;

using DatePriceDic = boost::multi_index_container<
    //data::Order,
    std::reference_wrapper<data::Order>,
    boost::multi_index::indexed_by<IdKey, DateKey, PriceKey>>;

using IdIndex = DatePriceDic::index<IdTag>::type;
using DateIndex = DatePriceDic::index<DateTag>::type;
using PriceIndex = DatePriceDic::index<PriceTag>::type;

and found examples with equal_range and lower_bound, but as far as I can guess I need something else.

1

There are 1 best solutions below

7
Joaquín M López Muñoz On

You have to do it in two steps:

  • Retrieve the orders using the PriceTag index and store the results in a vector of reference wrappers.
  • Sort the vector.

Live Coliru Demo

#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/multi_index/global_fun.hpp>
#include <boost/multi_index/key.hpp>
#include <chrono>

namespace data
{
    using OrderId = int64_t;
    using TimePoint = std::chrono::steady_clock::time_point;
    
    struct Order
    {
        OrderId id;

        double price;

        TimePoint updateTime;

        double average() const
        {
            return price;
        }
    };
}

struct IdTag {};
struct DateTag {};
struct PriceTag {};

using IdKey = boost::multi_index::ordered_unique<boost::multi_index::tag<IdTag>, boost::multi_index::key<&data::Order::id>>;
using DateKey = boost::multi_index::ordered_non_unique<boost::multi_index::tag<DateTag>, boost::multi_index::key<&data::Order::updateTime>>;
using PriceKey = boost::multi_index::ordered_non_unique<boost::multi_index::tag<PriceTag>, boost::multi_index::key<&data::Order::price>>;

using DatePriceDic = boost::multi_index_container<
    //data::Order,
    std::reference_wrapper<data::Order>,
    boost::multi_index::indexed_by<IdKey, DateKey, PriceKey>>;

using IdIndex = DatePriceDic::index<IdTag>::type;
using DateIndex = DatePriceDic::index<DateTag>::type;
using PriceIndex = DatePriceDic::index<PriceTag>::type;

#include <algorithm>
#include <iostream>
#include <vector>

int main()
{
    using namespace std::literals;
 
    auto now = std::chrono::steady_clock::now();
    data::Order dat[] = {
        {0, 100.0, now},
        {1, 100.0, now-24h},
        {2, 150.0, now-24h},
        {3, 200.0, now},
    };
    DatePriceDic dic;
    for(auto& o: dat) dic.insert(std::ref(o));
    
    auto p = 100.0;
    
    // retrieve orders with price>p
    std::vector<std::reference_wrapper<data::Order>> rng = 
        {dic.get<PriceTag>().upper_bound(p), dic.get<PriceTag>().end()};
        
    // sort by descending udpdateTime
#if __cplusplus >= 202002L // we can use ranges in C++20
    std::ranges::sort(rng, std::greater{}, &data::Order::updateTime);
#else
    std::sort(
        rng.begin(), rng.end(),
        [](const data::Order& o1, const data::Order& o2){ return o1.updateTime > o2.updateTime;});
#endif
        
    for(const data::Order& o: rng){
        std::cout<<o.id<<": "<<o.price<<" "<<(now-o.updateTime).count()<<"\n";
    }
    
    return 0;
}

Output

3: 200 0
2: 150 86400000000000