Combining n vectors into one vector of n-tuples

南笙酒味 提交于 2021-01-27 11:44:35

问题


I'm thinking about a function with signature

template<typename ...Ts>
std::vector<std::tuple<Ts...>> join_vectors(std::vector<Ts>&&...) {
    //...
};

but probably a more general one accepting any iterable instead of just std::vector would be good. Probably it would have a signature like this?

template<template<typename> typename C, typename ...Ts>
C<std::tuple<Ts...>> join_vectors(C<Ts>&&...) {
    // ...
};

However, I'm not at this level yet in C++ (despite doing the same in Haskell would be relatively easy), hence I seek for help.

Unfortunately, Range-v3's zip is not at my disposal in this case. I'm tagging it because I think those interested in it are in a better position to help me.


回答1:


For any indexable containers with size something like this is possible:

#include <tuple>
#include <vector>
#include <algorithm>

// Copy from lvalue containers, move from rvalue containers.
template<typename ...Cs>
auto zip(Cs... vecs) {
    std::vector<std::tuple<typename std::decay_t<Cs>::value_type...>> vec;

    auto len = std::min({vecs.size()...});
    vec.reserve(len);
    for(std::size_t i=0;i<len;++i){
        vec.emplace_back(std::move(vecs[i])...);
    }
    return vec;
};

//Return vector of tuples with & for non-const vecs and const& if const.
template<typename ...Cs>
auto zip_view(Cs&... vecs) {
    std::vector<std::tuple<decltype(vecs[0])...>> vec;
    auto len = std::min({vecs.size()...});
    vec.reserve(len);
    for(std::size_t i=0;i<len;++i){
        vec.emplace_back(vecs[i]...);
    }
    return vec;
};

If the containers have properly implemented move constructors, this solution will copy the containers passed as lvalues and move from rvalue ones. Very slight downside is that lvalue containers are copied whole first instead of only the individual elements.

Example [Godbolt]

#include <iostream>
#include <memory>
template<typename T, typename...Args>
void print_tuple(const T& first, const Args&... args){
    std::cout<<'('<<first;
    ((std::cout<<','<< args),...);
    std::cout<<')';
}

template<typename T>
struct helper{
using fnc_t = void;
};
template<typename...Args>
struct helper<std::tuple<Args...>>{
using fnc_t = void(*)(const Args&... args);
};
template<typename...Args>
struct helper<std::tuple<Args&...>>{
using fnc_t = void(*)(const Args&... args);
};

template<typename T>
using fnc_t2 = typename helper<T>::fnc_t;

template<typename T>
void template_apply(fnc_t2<T> f, const T& tuple){
    std::apply(f, tuple);
}

template<typename T>
void print_vec(const std::vector<T>& vec){
    for(const auto&e:vec){
        template_apply(print_tuple,e);
        std::cout<<'\n';
    }
}
struct MoveOnlyFoo{
    MoveOnlyFoo(int i):m_i(i){}

    int m_i;
    std::unique_ptr<int> ptr = nullptr;
};
    std::ostream& operator<<(std::ostream& o, const MoveOnlyFoo& foo){
        return o<<foo.m_i;
    }


int main(){
    std::vector v1{1,2,3,4,5,6};
    std::vector v2{'a','b','c','d','e'};
    std::vector v3{1.5,3.5,7.5};
    std::vector<MoveOnlyFoo> vmove;
    vmove.emplace_back(45);
    vmove.emplace_back(46);
    vmove.emplace_back(47);
    const std::vector v4{-1,-2,-3,-4,-5};

    //Move rvalues, copy lvalue.
    print_vec(zip(v1,v2,v3, v4, std::move(vmove)));
    // This won't work since the elements from the last vector cannot be copied.
    //print_vec(zip(v1,v2,v3, v4, vmove));
    std::cout<<"View:\n";
    //View, provides const& for const inputs, & for non-const
    print_vec(zip_view(v1,v2,v3,v4));
    std::cout<<"Modify and print:\n";
    for(auto& [x,y]: zip_view(v1,v2)){
        ++x,++y;
    }
    // Note the view can work with const containers, returns tuple of `const T&`.
    print_vec(zip_view(std::as_const(v1),std::as_const(v2)));
}

Output

(1,a,1.5,-1,45)
(2,b,3.5,-2,46)
(3,c,7.5,-3,47)
View:
(1,a,1.5,-1)
(2,b,3.5,-2)
(3,c,7.5,-3)
Modify and print:
(2,b)
(3,c)
(4,d)
(5,e)
(6,f)

Please disregard the readability of the printing code ;)

I modeled it after python zip functionality. Note your initial proposal copies the vectors, so the output is a vector with the values moved from the parameters.

Returning an iterable Cs is harder because you would have to specify how to insert elements into it, iterators cannot do it on their own.

Getting it work with iterators (but returning still a vector) is a chore, but in theory also possible.



来源:https://stackoverflow.com/questions/64985752/combining-n-vectors-into-one-vector-of-n-tuples

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!