Ludovic Aubert
Ludovic Aubert

Reputation: 10538

use C++ range set_intersection() as SQL Join

#include <string>
#include <vector>
#include <algorithm>
using namespace std;

struct Item1{int id; int data;};
vector<Item1> v1 = {{.id=1,.data=42},{.id=2,.data=69}};
struct Item2{int id; string data;};
vector<Item2> v2 = {{.id=1, .data="banana"},{.id=3,.data="coconut"}};

vector<int> select;

auto ret = ranges::set_intersection(v1,v2,back_inserter(select),{},&Item1::id,&Item2::id);

How do I get the result which should be

[[{.id=1,.data=42}, {.id=1, .data="banana"}]]

?

Upvotes: 0

Views: 90

Answers (2)

Ludovic Aubert
Ludovic Aubert

Reputation: 10538

#include <string>
#include <vector>
#include <algorithm>
using namespace std;
using namespace ranges;
    
struct Item1{int id; int data;};
vector<Item1> v1 = {{.id=1,.data=42},{.id=2,.data=69}};
struct Item2{int id; string data;};
vector<Item2> v2 = {{.id=1, .data="banana"},{.id=3,.data="coconut"}};
    
vector<int> select;
    
auto ret = ranges::set_intersection(
   v1 | views::transform(&Item1::id),
   v2 | views::transform(&Item2.id),
   back_inserter(select)
);
    
auto rg = select | views::transform([&](int id){return make_tuple(v1[id], v2[id]);});

Upvotes: 0

Caleth
Caleth

Reputation: 62874

std::ranges::set_intersection doesn't do what you want here

If some element is found m times in [first1, last1) and n times in [first2, last2), the first min(m, n) elements will be copied from the first range to result.

You need a different algorithm, one that will use the values from both ranges. Adapting the possible implementation of set_intersection

template< class I1, class I2, class O, class F, class Comp = std::ranges::less, class Proj1 = std::identity, class Proj2 = std::identity >
concept joinable =
    std::input_iterator<I1> &&
    std::input_iterator<I2> &&
    std::weakly_incrementable<O> &&
    std::indirectly_writable<O, std::indirect_result_t<F&, I1, I2>> &&
    std::indirect_strict_weak_order<Comp,
                                    std::projected<I1, Proj1>,
                                    std::projected<I2, Proj2>>;

struct join_fn {
    template< std::input_iterator I1, std::sentinel_for<I1> S1,
              std::input_iterator I2, std::sentinel_for<I2> S2,
              std::weakly_incrementable O, 
              class Comp = std::ranges::less,
              std::copy_constructible F,
              class Proj1 = std::identity, class Proj2 = std::identity >
    requires joinable<I1, I2, O, F, Comp, Proj1, Proj2>
    constexpr std::ranges::binary_transform_result<I1, I2, O>
    operator()( I1 first1, S1 last1, I2 first2, S2 last2, O result, F binary_op, Comp comp, Proj1 proj1 = {}, Proj2 proj2 = {} ) const {
        while (!(first1 == last1 or first2 == last2)) {
            if (std::invoke(comp, std::invoke(proj1, *first1), std::invoke(proj2, *first2)))
                ++first1;
            else if (std::invoke(comp, std::invoke(proj2, *first2), std::invoke(proj1, *first1)))
                ++first2;
            else
                *result = std::invoke(binary_op, *first1, *first2), ++result, ++first1, ++first2;
        }
        return {std::ranges::next(std::move(first1), std::move(last1)),
                std::ranges::next(std::move(first2), std::move(last2)),
                std::move(result)};
    }
 
    template< std::ranges::input_range R1, std::ranges::input_range R2,
              std::weakly_incrementable O, 
              class Comp = std::ranges::less,
              std::copy_constructible F, 
              class Proj1 = std::identity, class Proj2 = std::identity >
    requires joinable<std::ranges::iterator_t<R1>, std::ranges::iterator_t<R2>,
             O, F, Comp, Proj1, Proj2>
    constexpr std::ranges::binary_transform_result<std::ranges::borrowed_iterator_t<R1>,
              std::ranges::borrowed_iterator_t<R2>, O>
    operator()( R1&& r1, R2&& r2, O result, F binary_op, Comp comp = {},
                Proj1 proj1 = {}, Proj2 proj2 = {} ) const {
        return (*this)(std::ranges::begin(r1), std::ranges::end(r1),
                       std::ranges::begin(r2), std::ranges::end(r2),
                       std::move(result), std::move(binary_op), std::move(comp),
                       std::move(proj1), std::move(proj2));
    }
} join;

And then you'll need to put them into a container that can hold the joined type.

struct ItemCombined{ int id; int data1; std::string data2; };

ItemCombined combine(Item1, Item2); // to implement

Finally you can

std::vector<Item1> v1 = { {.id=1, .data=42 }, {.id=2, .data=69 } };
std::vector<Item2> v2 = { {.id=1, .data="banana"s }, {.id=3, .data="coconut"s } };

std::vector<ItemCombined> select;

join(v1,v2,std::back_inserter(select),{},combine,&Item1::id,&Item2::id);

See it on coliru

Upvotes: 1

Related Questions